/*
 * libsyncml - A syncml protocol implementation
 * Copyright (C) 2005  Armin Bauer <armin.bauer@opensync.org>
 * Copyright (C) 2007-2008  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_queue_internals.h"
#include "sml_session_internals.h"
#include "sml_command_internals.h"
#include "sml_elements_internals.h"
#include "sml_parse_internals.h"
#include "sml_transport_internals.h"
#include "objects/sml_auth_internals.h"

/**
 * @defgroup SmlSessionPrivate SyncML Session Private API
 * @ingroup PrivateLowLevelAPI
 * @brief Private Interfaces to create, manipulate and delete sessions
 * 
 */
/*@{*/

typedef enum SmlSessionCommandType {
	SML_SESSION_COMMAND_START,
	SML_SESSION_COMMAND_END,
	SML_SESSION_STATUS,
	SML_SESSION_FLUSH
} SmlSessionCommandType;

typedef struct SmlSessionCommand {
	SmlSessionCommandType type;
	SmlCommand *cmd;
	SmlCommand *parent;
	SmlStatus *status;
	SmlStatusReplyCb callback;
	void *callbackUserdata;
	SmlBool final;
	SmlBool end;
} SmlSessionCommand;

void smlSessionDispatchEvent(SmlSession *session, SmlSessionEventType type, SmlCommand *cmd, SmlCommand *parent, SmlStatus *headerreply, SmlError *error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p, %p, %p, %p)", __func__, session, type, cmd, parent, headerreply, error);
	smlAssert(session);
	smlAssert(session->eventCallback);
	
	session->eventCallback(session, type, cmd, parent, headerreply, error, session->eventCallbackUserdata);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

static void _smlSessionFreePendingReplies(SmlSession *session)
{
	while (session->pendingReplies) {
		smlSafeFree((gpointer *)&(session->pendingReplies->data));
		session->pendingReplies = g_list_delete_link(session->pendingReplies, session->pendingReplies);
	}
}

/** @brief Flushes a session
 * 
 * This flushes the already added statuses and/or commands. Note that if the session
 * has a size limitation, it will get flushed automatically if the size of the already
 * added commands exceeds the limit. Use this function if you want for some reason
 * to flush manually.
 * 
 * @param session The session to flush
 * @param final Set to TRUE if you want to have the final flag added. Otherwise it will just flush
 * the already added statuses and commands
 * @param error A error struct
 * @return TRUE if successful, FALSE otherwise
 * 
 */
static SmlBool _smlSessionFlushInternal(SmlSession *session, SmlBool final, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p)", __func__, session, final, error);
	CHECK_ERROR_REF
	smlAssert(session);
	smlAssert(session->dataCallback);
	
	char *data = NULL;
	unsigned int size = 0;
	SmlBool end = FALSE;
	
	/* Add the latest header */
	if (!smlAssemblerAddHeader(session->assembler, session, error))
		goto error;
	
	/* Get the data that is waiting in the assembler */
	if (!smlAssemblerRun(session->assembler, &data, &size, &end, final, error))
		goto error;
	if (session->authenticate)
		end = FALSE;
	
	/* Flush and reset the command ID counter */
	if (session->lastCommandID > 1)
	{
		smlAssemblerFlush(session->assembler);
	} else {
		session->lastCommandID = smlAssemblerFlush(session->assembler) - 1;
	}
	smlTrace(TRACE_INTERNAL, "%s: flushed assembler - last command id is %i now",
		__func__, session->lastCommandID);
	
	SmlTransportData *tspdata = smlTransportDataNew(data, size, session->type, TRUE, error);
	if (!tspdata)
		goto error_free_data;
	data = NULL;
	
	if (end && session->sessionType == SML_SESSION_TYPE_SERVER)
		tspdata->needsAnswer = FALSE;
	
	/* Send the flush event */
	if (!session->authenticate)
		smlSessionDispatchEvent(session, SML_SESSION_EVENT_FLUSH, NULL, NULL, NULL, NULL);
	
	/* Send the data over the transport */
	session->dataCallback(session, tspdata, session->dataCallbackUserdata);
		
	smlTransportDataDeref(tspdata);
	
	session->waiting = TRUE;
	
	if (final)
		session->sending = FALSE;
		
	if (end && session->sessionType == SML_SESSION_TYPE_SERVER) {
		smlTrace(TRACE_INTERNAL, "%s: Ending session now", __func__);
		session->end = TRUE;
		smlSessionRestoreTargetURI(session);
		g_mutex_lock(session->reportEnd);
		if (!session->reportedEnd) {
			/* The end should only be reported once. */
			session->reportedEnd = TRUE;
			smlSessionDispatchEvent(session, SML_SESSION_EVENT_END,
				 NULL, NULL, NULL, NULL);
		}
		g_mutex_unlock(session->reportEnd);
	}
	
	smlTrace(TRACE_INTERNAL, "%s: incrementing lastMessageID %d (old)",
		__func__, session->lastMessageID);
	session->lastMessageID++;
	
	session->hasCommand = FALSE;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

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

static void _check_reply(SmlSession *session, SmlStatus *status, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, status, userdata);

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

static void _smlSessionFragmentStatus(SmlSession *session, SmlStatus *status, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, status, userdata);
	//SmlError *error = NULL;
	
	smlTrace(TRACE_INTERNAL, "%s: Got a status reply %i", __func__, status->type);
	
	if (smlStatusGetCode(status) != SML_CHUNK_ACCEPTED) {
		/* We need to send a error reply for the original command */
		
		smlCommandUnref(session->frag_command);
		session->frag_command = NULL;
	}

	smlTrace(TRACE_EXIT, "%s", __func__);
	return;
	
/*error:
	smlSessionDispatchEvent(session, SML_SESSION_EVENT_ERROR, NULL, NULL, NULL, error);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&error));
	smlErrorDeref(&error);
	return;*/
}

static SmlCommand *_smlSessionFragmentSend(SmlSession *session, SmlCommand *orig_cmd, SmlCommand *parent, unsigned int space, unsigned int start, unsigned int complete_size, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %i, %i, %i, %p)", __func__, session, orig_cmd, parent, space, start, complete_size, error);
	CHECK_ERROR_REF
	/* This library must not send more that one item per change
	 * command. This is compliant with all specification and only a
	 * self limitation.
	 */
	smlAssert(g_list_length(orig_cmd->private.change.items) == 1);
	SmlCommand *frag_cmd = NULL;
	
	char *data = NULL;
	unsigned int size = 0;
	SmlItem *orig_item = g_list_nth_data(orig_cmd->private.change.items, 0);
	if (!smlItemGetData(orig_item, &data, &size, error))
		goto error;

	// FIXME: should we add an assertion here to guarantee
	// FIXME: that size and complete_size are equal

	if (space < size - start) {
		/* We need to create a new command. But we only send as much data as space
		 * is available */
		frag_cmd = smlCommandNew(orig_cmd->type, error);
		if (!frag_cmd)
			goto error;

		SmlItem *frag_item = smlItemNewForData(data + start, space, error);
		if (!frag_item)
			goto error_free_cmd;
		frag_cmd->private.change.items = g_list_append (NULL, frag_item);
		if (!frag_cmd->private.change.items)
		{
			smlItemUnref(frag_item);
			goto error_free_cmd;
		}
		
		frag_item->moreData = TRUE;
		
		frag_item->target = orig_item->target;
		if (frag_item->target)
			smlLocationRef(frag_item->target);
		
		frag_item->source = orig_item->source;
		if (frag_item->source)
			smlLocationRef(frag_item->source);
		
		frag_item->contenttype = g_strdup(orig_item->contenttype);

		/* If this is the first sent part of the fragmented command
		 * then this command must include the size of the complete
		 * command.
		 */
		if (start == 0)
			frag_cmd->size = complete_size;
			
		session->frag_size += space;
	} else {
		/* We have enough room to send the original command.
		 * Nevertheless the original item must be replaced
		 * with a new item which contains the rest of the data.
		 * The original item must be freed.
		 */

		/* get original item and cleanup item list */
		smlAssert(g_list_length(orig_cmd->private.change.items) == 1);
		smlAssert(g_list_remove(orig_cmd->private.change.items, orig_item) == NULL);
		orig_cmd->private.change.items = NULL;

		/* setup new item */
		SmlItem *frag_item = smlItemNewForData(data + start, size - start, error);
		if (!frag_item) {
			smlItemUnref(orig_item);
			goto error;
		}
		orig_cmd->private.change.items = g_list_append(NULL, frag_item);
		if (!orig_cmd->private.change.items)
		{
			smlItemUnref(orig_item);
			smlItemUnref(frag_item);
			goto error;
		}

		/* configure new item */
		frag_item->contenttype = g_strdup(orig_item->contenttype);
		frag_item->target = orig_item->target;
		if (frag_item->target)
			smlLocationRef(frag_item->target);
		frag_item->source = orig_item->source;
		if (frag_item->source)
			smlLocationRef(frag_item->source);
		
		/* cleanup original item */
		smlItemUnref(orig_item);
		
		/* setup fragmentation command */
		frag_cmd = orig_cmd;
		session->frag_size += size - start;
	}
	
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, frag_cmd);
	return frag_cmd;
	
error_free_cmd:
	smlCommandUnref(frag_cmd);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return NULL;
}

static SmlAssemblerResult _smlSessionStartCommandInternal(SmlSession *session, SmlCommand *cmd, SmlCommand *parent, SmlStatusReplyCb callback, void *userdata, SmlBool addToCommandStack, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p, %p, %i, %p)", __func__, session, cmd, parent, callback, userdata, addToCommandStack, error);
	smlAssert(session);
	smlAssert(cmd);
	CHECK_ERROR_REF
	SmlPendingStatus *pending = NULL;
	SmlBool fragmented = FALSE;
	
	if (parent && !session->hasCommand) {
		GList *c = NULL;
		SmlCommand *oldcmd = NULL;
		SmlCommand *oldparent = NULL;
		GList * cmds = g_list_copy(session->commandStack);
		for (c = cmds; c; c = c->next) {
			oldcmd = c->data;
		
			if (_smlSessionStartCommandInternal(session, oldcmd, oldparent, _check_reply, NULL, FALSE, error) != SML_ASSEMBLER_RESULT_OK)
				goto error;
			oldparent = oldcmd;
		}
		g_list_free(cmds);
	}
	
	/* Large object handling. Only possible of the session has a limit and
	 * we dont have a 1.0 session */
	if ((cmd->type == SML_COMMAND_TYPE_ADD || cmd->type == SML_COMMAND_TYPE_REPLACE) &&
	    smlAssemblerGetRemoteMaxMsgSize(session->assembler) > 0 && session->version != SML_VERSION_10) {
		
		smlTrace(TRACE_INTERNAL, "%s: Checking if command needs to be fragmented", __func__);
		/* This library must not send more that one item per change
		 * command. This is compliant with all specification and only a
		 * self limitation.
		 */
		smlAssert(g_list_length(cmd->private.change.items) == 1);
		
		char *data = NULL;
		unsigned int size = 0;
		SmlItem *item = g_list_nth_data(cmd->private.change.items, 0);
		if (!smlItemGetData(item, &data, &size, error))
			goto error;
		
		/* If max obj size is not unlimited (0), and the size of the item is larger
		 * than the max obj size, we have to return an error */
		int sendingmaxobjsize = smlSessionGetRemoteMaxObjSize(session);
		if ((sendingmaxobjsize > 0) &&
		    (size > (unsigned int)sendingmaxobjsize))
		{
			smlErrorSet(error, SML_ERROR_SIZE_MISMATCH, "Item (%ub) is larger than the limit (%db)", size, smlSessionGetRemoteMaxObjSize(session));
			goto error;
		}
		
		if (!session->frag_command)
			cmd->size = size;
		item->moreData = TRUE;
		int space = 0;
		if (!smlAssemblerGetSpace(session->assembler, &space, parent, cmd, error))
			goto error;
		cmd->size = 0;
		item->moreData = FALSE;
		
		/* Check if item data fits into the current message */
		if (session->frag_command ||
		    (space > 0 &&
		     (unsigned int) space < size - session->frag_size))
		{
			smlTrace(TRACE_INTERNAL, "%s: Space %i, size %i. Fragmenting. Already added: %i", __func__, space, size - session->frag_size, session->frag_size);
			/* We need to fragment */
			/* Store the command for processing */
			
			if (!session->frag_command) {
				session->frag_size = 0;
				
				session->frag_command = cmd;
				smlCommandRef(cmd);
				
				session->frag_callback = callback;
				session->frag_userdata = userdata;
			}
			
			if (!(cmd = _smlSessionFragmentSend(session, session->frag_command, parent, space, session->frag_size, size, error)))
				goto error;
			
			callback = _smlSessionFragmentStatus;
			userdata = NULL;
			fragmented = TRUE;

			/* Sometime cmd is frag_command. So theoretically it can
			 * happen that the item is replaced. Therefore the item
			 * reference must be determined again.
			 */
			item = g_list_nth_data(cmd->private.change.items, 0);
			if (item->moreData == FALSE) {
				smlTrace(TRACE_INTERNAL, "%s: This is the last chunk", __func__);
				
				callback = session->frag_callback;
				userdata = session->frag_userdata;
				fragmented = FALSE;
				
				session->frag_size = 0;
				
				smlCommandUnref(session->frag_command);
				session->frag_command = NULL;
			}
			
			smlTrace(TRACE_INTERNAL, "%s: Fragmented. Added %i already", __func__, session->frag_size);
		}
	}
	
	/* We now increment the session ID */
	session->lastCommandID++;
	cmd->cmdID = session->lastCommandID;
	smlTrace(TRACE_INTERNAL, "%s: last command id is %i", __func__, session->lastCommandID);
	
	/* Now we can try to add the command to the assembler */
	switch (smlAssemblerStartCommand(session->assembler, parent, cmd, error)) {
		case SML_ASSEMBLER_RESULT_OK:
			/* We successfully added the command */
			cmd->pushedBack = FALSE;
			break;
		case SML_ASSEMBLER_RESULT_MISMATCH:
			if (cmd->pushedBack) {
				smlErrorSet(error, SML_ERROR_GENERIC, "Command is too large");
				goto error;
			}
			cmd->pushedBack = TRUE;
			
			/* We werent able to add the command. So we have to flush the already
			 * added statuses/commands and then add it again later */
			if (!_smlSessionFlushInternal(session, FALSE, error)) {
				session->lastCommandID--;
				goto error;
			}
			
			smlTrace(TRACE_EXIT, "%s: Mismatch", __func__);
			return SML_ASSEMBLER_RESULT_MISMATCH;
		case SML_ASSEMBLER_RESULT_ERROR:
			session->lastCommandID--;
			goto error;
	}
	
	/* If the user requested a reply to the command we generate
	 * the pending status so that we can dispatch the incoming status
	 */
	if (callback) {
		pending = smlTryMalloc0(sizeof(SmlPendingStatus), error);
		if (!pending) {
			session->lastCommandID--;
			goto error;
		}
		
		pending->callback = callback;
		pending->userdata = userdata;
		pending->cmdID = cmd->cmdID;
		pending->msgID = session->lastMessageID;
		
		smlTrace(TRACE_INTERNAL, "%s: Appending pending status with cmdID %i and msgID %i", __func__, pending->cmdID, pending->msgID);
		session->pendingReplies = g_list_append(session->pendingReplies, pending);
	} else
		cmd->noResp = TRUE;
	
	/* We will get a status and a result to a get command
	 * so we register a second callback for the result */
	if (cmd->type == SML_COMMAND_TYPE_GET) {
		pending = smlTryMalloc0(sizeof(SmlPendingStatus), error);
		if (!pending) {
			session->lastCommandID--;
			goto error;
		}
		
		pending->callback = callback;
		pending->userdata = userdata;
		pending->cmdID = cmd->cmdID;
		pending->msgID = session->lastMessageID;
		
		smlTrace(TRACE_INTERNAL, "%s: Appending pending status for a result with cmdID %i and msgID %i", __func__, pending->cmdID, pending->msgID);
		session->pendingReplies = g_list_append(session->pendingReplies, pending);
	}
	
	if (fragmented) {
		/* We werent able to add the command. So we have to flush the already
		 * added statuses/commands and then add it again later */
		if (!_smlSessionFlushInternal(session, FALSE, error)) {
			session->lastCommandID--;
			goto error;
		}
		
		smlCommandUnref(cmd);
		
		smlTrace(TRACE_EXIT, "%s: Mismatch but fragmented", __func__);
		return SML_ASSEMBLER_RESULT_MISMATCH;
	}
	
	if (addToCommandStack)
		session->commandStack = g_list_append(session->commandStack, cmd);
	session->hasCommand = TRUE;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return SML_ASSEMBLER_RESULT_OK;

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

static SmlBool _smlSessionEndCommandInternal(SmlSession *session, SmlCommand *parent, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, parent, error);
	smlAssert(session);
	CHECK_ERROR_REF
	
	if (!smlAssemblerEndCommand(session->assembler, parent, error))
		goto error;
	
	session->commandStack = g_list_delete_link(session->commandStack, g_list_last(session->commandStack));
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

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

static void _smlSessionCommandFree(SmlSessionCommand *sesscmd)
{
	if (sesscmd->cmd)
		smlCommandUnref(sesscmd->cmd);
		
	if (sesscmd->parent)
		smlCommandUnref(sesscmd->parent);
		
	if (sesscmd->status)
		smlStatusUnref(sesscmd->status);
	
	smlSafeFree((gpointer *)&sesscmd);
}

static void _smlSessionCommandHandler(void *message, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	smlAssert(message);
	smlAssert(userdata);
	SmlSessionCommand *sesscmd = message;
	SmlSession *session = userdata;
	SmlError *error = NULL;
	//SmlPendingStatus *pending = NULL;
	smlTrace(TRACE_INTERNAL, "%s: session cmd type %i", __func__, sesscmd->type);

	/* check if the header is ready */
	if (!session->assmHasHeader)
	{
		if (!smlAssemblerAddHeader(session->assembler, session, &error))
			goto error;
		session->assmHasHeader = TRUE;
	}

	SmlCommand *parent = sesscmd->parent;
	SmlCommand *cmd = sesscmd->cmd;
	SmlStatus *status = sesscmd->status;
	
	switch (sesscmd->type) {
		case SML_SESSION_FLUSH:
			if (sesscmd->end) {
				session->end = TRUE;
				smlTrace(TRACE_INTERNAL, "%s: End on flush was set", __func__);
			}
			if (!_smlSessionFlushInternal(session, sesscmd->final, &error))
				goto error;
			break;
		case SML_SESSION_COMMAND_END:
			if (!_smlSessionEndCommandInternal(session, parent, &error))
				goto error;
			break;
		case SML_SESSION_COMMAND_START:
			switch (_smlSessionStartCommandInternal(session, cmd, parent, sesscmd->callback, sesscmd->callbackUserdata, TRUE, &error)) {
				case SML_ASSEMBLER_RESULT_OK:
					/* We successfully added the command */
					break;
				case SML_ASSEMBLER_RESULT_MISMATCH:
					smlQueuePushHead(session->command_queue, message);
					return;
				case SML_ASSEMBLER_RESULT_ERROR:
					goto error;
			}
			break;
		case SML_SESSION_STATUS:;
			unsigned int cmdRef, msgRef = 0;
			if (!smlAssemblerGetNextCmdRef(session->assembler, &cmdRef, &msgRef)) {
				smlErrorSet(&error, SML_ERROR_GENERIC, "No more statuses were needed");
				goto error;
			}
			
			if (status->cmdRef != cmdRef || status->msgRef != msgRef) {
				//Put it on the end of the queue
				smlQueueSendPrio(session->command_queue, message);
				smlTrace(TRACE_EXIT,
					"%s - next needed command status %d for message %d is not this one (cmd %d, msg %d)",
					__func__, cmdRef, msgRef, status->cmdRef, status->msgRef);
				return;
			}
			
			/* We ignore if the added status violates the size limitation if
			 * a incoming buffer for a large object is open. the problem is that
			 * the status for the chunk has to go into the next message AND the status
			 * MUST be in the same order as the original commands. so the only solution is
			 * to send all statuses and ignore the size. */
			
			/* Now we can try to add the status to the assembler */
			smlTrace(TRACE_INTERNAL, "%s - try to add status", __func__);
			switch (smlAssemblerAddStatusFull(session->assembler, status, session->incomingBuffer ? TRUE : FALSE, &error)) {
				case SML_ASSEMBLER_RESULT_OK:
					/* We successfully added the status */
					session->active = TRUE;
					break;
				case SML_ASSEMBLER_RESULT_MISMATCH:
					/* We werent able to add the command. So we have to flush the already
					 * added statuses/commands and then add it again later */
					if (!_smlSessionFlushInternal(session, FALSE, &error)) {
						session->lastCommandID--;
						goto error;
					}
					
					/* Push the message back into the queue */
					smlQueuePushHeadPrio(session->command_queue, message);
					smlTrace(TRACE_EXIT, "%s - status cannot be added, so push back", __func__);
					return;
					break;
				case SML_ASSEMBLER_RESULT_ERROR:
					goto error;
			}
			break;
	}
	
	_smlSessionCommandFree(sesscmd);
	smlTrace(TRACE_EXIT, "%s", __func__);
	
	return;

error:
	smlSessionDispatchEvent(session, SML_SESSION_EVENT_ERROR, NULL, NULL, NULL, error);
	_smlSessionCommandFree(sesscmd);
	return;
}

SmlBool smlSessionReceiveHeader(SmlSession *session, SmlHeader *header, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, header, error);
	smlAssert(session);
	smlAssert(header);
	CHECK_ERROR_REF

	/* check and set the counters */
	if (header->messageID <= session->lastReceivedMessageID) {
		smlErrorSet(error, SML_ERROR_GENERIC, "Message ID not incremented");
		goto error;
	}
	session->lastReceivedMessageID = header->messageID;
	session->lastCommandID++;

	/* check and set the MaxMsgSize */
	if (0 < header->maxmsgsize) {
		/* If MaxMsgSize is 0 then it is not set. Therefore
		 * a MaxMsgSize of 0 is ignored to not override an
		 * already configured MaxMsgSize from an earlier
		 * message.
		 */
		smlAssemblerSetRemoteMaxMsgSize(session->assembler, header->maxmsgsize);
	}
	if (0 < header->maxmsgsize && header->maxmsgsize < session->localMaxMsgSize)
	{
		/* The localMaxMsgSize is only set if it is already
		 * configured. If there is no local limit then it is not
		 * necessary to configure it only because the remote
		 * peer has a limit.
		 */
		smlSessionSetLocalMaxMsgSize(session, header->maxmsgsize);
	}
	
	/* Reserve the status for header reply. It will always be cmdRef 0
	 * and it will always be the first command in the message */
	if (!smlAssemblerReserveStatus(session->assembler, 0, header->messageID, 1, error))
		goto error;

	/* If RespURI is send in the header
	 * then the URI in the transport layer must be adjusted. */
	if (header->responseURI) {
		if (!session->orgTarget) {
			/* If the original target is already set
			 * then it MUST NOT be set again
			 * because this would destroy the real original
			 * target and an earlier RespURI would be used
			 * as original target.
			 */
			session->orgTarget = session->target;
			session->target = NULL;
		}

		if (session->target) {
			/* This is not the original target.
			 * Perhaps this is from an earlier message.
			 * Nevertheless only the most actual target
			 * is required.
			 */
			smlLocationUnref(session->target);
		}

		/* build the new target and publish it*/
		session->target = smlLocationNew(
					header->responseURI,
					smlLocationGetName(session->orgTarget),
					error);
		if (!session->target)
			goto error;
		smlSessionDispatchEvent(session, SML_SESSION_EVENT_RESPONSE_URI, NULL, NULL, NULL, NULL);
		smlTrace(TRACE_INTERNAL, "%s: RespURI: %s, Target: %s", __func__,
			VA_STRING(smlLocationGetURI(session->target)),
			VA_STRING(smlLocationGetURI(session->orgTarget)));
	} else {
		/* Sometimes a client changes the source reference
		 * after the SAN was sent because the SAN does not
		 * include the correct IMEI.
		 *
		 * If the header includes a new SourceRef/LocURI
		 * then we have to change the TargetRef/LocURI
		 * of the session.
		 */
		const char *header_source = smlLocationGetURI(header->source);
		const char *session_target = smlLocationGetURI(session->target);
		if (header_source &&
		    session_target &&
		    strcmp(header_source, session_target))
		{
			/* session target needs an update */
			smlTrace(TRACE_INTERNAL,
				"%s: update session target to header source %s",
				__func__, header_source);
			smlLocationSetURI(session->target, header_source);
		}
	}
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

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

SmlBool smlSessionDispatchStatus(SmlSession *session, SmlStatus *status, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, status, error);
	smlAssert(status);
	CHECK_ERROR_REF
	
	if (status->cmdRef == 0) {
		smlAssert(status->type == SML_COMMAND_TYPE_HEADER);
		smlAssert(status->data);
		SmlErrorType errorCode = atoi(status->data);
		if (session->sessionType == SML_SESSION_TYPE_SERVER)
		{
			/* If this is an OMA DS server then the session
			 * authorization is done by the header callback.
			 * This is usually handled by functions named
			 * smlAuth*.
			 *
			 * If the OMA DS client code would be active
			 * then the remote client only have to send an
			 * error code 212 to force the server to accept
			 * the connection. So this is highly critical.
			 */
			SmlCommand *cmd = smlCommandNew(SML_COMMAND_TYPE_HEADER, error);
			smlSessionDispatchEvent(session, SML_SESSION_EVENT_HEADER_REPLY, cmd, NULL, status, NULL);
			smlCommandUnref(cmd);
		}
		else if (errorCode == SML_AUTH_ACCEPTED)
		{
			/* authentication succeeded
			 * we can remove the credentials from this session
			 * we do it now for better security
			 */
			smlTrace(TRACE_INTERNAL, "%s - authentication succeeded", __func__);
			if (session->cred != NULL)
			{
				smlCredUnref(session->cred);
				session->cred = NULL;
			}
			if (!session->established)
			{
				session->established = TRUE;
				smlSessionDispatchEvent(
					session, SML_SESSION_EVENT_ESTABLISHED,
					NULL, NULL, NULL, NULL);
			}
		}
		else if (errorCode == SML_ERROR_AUTH_REQUIRED)
		{
			/* If the requested authentication method violates the
			 * local security policy then the authentication must
			 * fail. Otherwise the password of the user can be
			 * compromized.
			 */
			smlTrace(TRACE_INTERNAL, "%s - authentication required", __func__);
			session->authenticate = TRUE;
			session->established = FALSE;
			smlAssemblerRestoreCommands(session->assembler);
			smlTrace(TRACE_INTERNAL, "%s - restored commands from previous message", __func__);
			smlAssert(status->chal);
			smlAssert(session->cred);
			if (session->cred->type == SML_AUTH_TYPE_MD5 &&
			    status->chal->type != SML_AUTH_TYPE_MD5)
			{
				smlErrorSet(error, SML_ERROR_AUTH_REJECTED, "The remote peer tries to enforce an authentication method which violates the local security policy (syncml:auth-md5 is required).");
				goto error;
			}
			smlTrace(TRACE_INTERNAL, "%s - authentication type conforms to security policy", __func__);

			/* build the authentication string */
			session->cred->data = smlAuthGetCredString(
						status->chal->type,
						session->cred->username,
						session->cred->password,
						status->chal->nonce_b64,
						error);
			if (!session->cred->data)
				goto error;
			smlTrace(TRACE_INTERNAL, "%s - credential string set", __func__);
		} else {
			if (!session->established)
			{
				session->established = TRUE;
				smlSessionDispatchEvent(
					session, SML_SESSION_EVENT_ESTABLISHED,
					NULL, NULL, NULL, NULL);
			}
			SmlCommand *cmd = smlCommandNew(SML_COMMAND_TYPE_HEADER, error);
			smlSessionDispatchEvent(session, SML_SESSION_EVENT_HEADER_REPLY, cmd, NULL, status, NULL);
			smlCommandUnref(cmd);
		}
		goto out;
	}
	smlTrace(TRACE_INTERNAL, "%s - no header status", __func__);
	
	/* Check if a handler for this object at this path has been installed.
	 * Please note that sometimes (e.g. GET command) a second handler
	 * was installed. So we cannot abort at the first found pending reply.
	 * We have to check every handler.
	 */
	GList *o = NULL;
	SmlBool foundPending = FALSE;
	for (o = session->pendingReplies; o; o = o ? o->next : NULL ) {
		SmlPendingStatus *pending = o->data;
		smlTrace(TRACE_INTERNAL, "%s: check cmd %i of msg %i", __func__, pending->cmdID, pending->msgID);
		if (pending->cmdID == status->cmdRef && pending->msgID == status->msgRef) {
			smlTrace(TRACE_INTERNAL, "%s - Found pending status %s of command %d in message %d",
				__func__, VA_STRING(status->data), status->cmdRef, status->msgRef);
			if (session->authenticate)
			{
				/* modify the pending reply
				 * If there is a required authentication
				 * then all commands must be send again.
				 * Please note that the simple change of the
				 * message ID only works because we never touch
				 * command ID.
				 */
				smlTrace(TRACE_INTERNAL, "%s - update message ID to %d",
					__func__, session->lastMessageID);
				pending->msgID++;
			} else {
				/* normal handling of the first pendingReply (the status one) */
				smlTrace(TRACE_INTERNAL, "%s - normal status", __func__);
				session->pendingReplies = g_list_remove(session->pendingReplies, pending);
			
				smlAssert(pending->callback);
				pending->callback(session, status, pending->userdata);
			
				smlSafeFree((gpointer *)&pending);

				/* We have to abort the for loop here
				 * because we MUST NOT delete a potential
				 * second pending reply. The GET command
				 * registers to pending replies - one for
				 * the status and one for the results.
				 */
				o = NULL;
			}
			
			foundPending = TRUE;
		}
	}
	if (foundPending)
		goto out;
	smlTrace(TRACE_INTERNAL, "%s - no pending reply found", __func__);

	/* We have to catch at this point if the status reply is the reply
	 * to our server alerted sync. Since this initial alert is not sent
	 * over a session, the status can of course not be wanted */
	if (status->type == SML_COMMAND_TYPE_ALERT && !status->anchor) {
		smlTrace(TRACE_INTERNAL, "%s: Handling status for server alerted sync", __func__);
		SmlErrorType errorCode = atoi(status->data);
		if ((199 < errorCode && errorCode < 300) ||
		    errorCode == 508)
		{
			/* If the status is 2xx or 508 then it is no error. */
			goto out;
		} else {
			/* There is an error and this must be signaled.
			 * If there is no callback available
			 * then this is a normal error.
			 */
			smlErrorSet(error, errorCode, "The SAN alert was rejected.");
			goto error;
		}
	}
	
	smlErrorSet(error, SML_ERROR_GENERIC,
		"Received unwanted status %s of command %d in message %d",
		status->data, status->cmdRef, status->msgRef);
	goto error;
out:
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

SmlBool smlSessionCheck(SmlSession *session)
{
	smlAssert(session);
	
	if (session->waiting)
	{
		smlTrace(TRACE_INTERNAL, "%s: waiting", __func__);
		return FALSE;
	}

	SmlBool result;
	if (!smlAssemblerIsStatusMissing(session->assembler))
	{
		result = smlQueueCheck(session->command_queue);
		smlTrace(TRACE_INTERNAL, "%s: no status is missing - %i", __func__, result);
	}
	else
	{
		result = smlQueueCheckPrio(session->command_queue);
		smlTrace(TRACE_INTERNAL, "%s: status is missing - %i", __func__, result);
	}
	return result;
}

SmlBool smlSessionTryLock(SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, session);
	smlAssert(session);
	smlAssert(session->dispatch_mutex);
	SmlBool ret = g_mutex_trylock(session->dispatch_mutex);
	smlTrace(TRACE_EXIT, "%s - %d", __func__, ret);
	return ret;
}

void smlSessionLock(SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, session);
	smlAssert(session);
	smlAssert(session->dispatch_mutex);
	g_mutex_lock(session->dispatch_mutex);
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void smlSessionUnlock(SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	smlAssert(session);
	smlAssert(session->dispatch_mutex);
	g_mutex_unlock(session->dispatch_mutex);
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void smlSessionDispatch(SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	smlAssert(session);
	
	if (session->waiting)
	{
		smlTrace(TRACE_EXIT, "%s - session is waiting", __func__);
		return;
	}
	
	/* If we are still missing statuses, we just dispatch the statuses
	 * If we are complete on the status side, we dispatch the commands
	 */
	if (!smlAssemblerIsStatusMissing(session->assembler))
	{
		smlTrace(TRACE_INTERNAL, "%s: dispatch commands", __func__);
		smlQueueDispatch(session->command_queue);
	}
	else
	{
		smlTrace(TRACE_INTERNAL, "%s: dispatch status", __func__);
		smlQueueDispatchPrio(session->command_queue);
	}
	smlTrace(TRACE_EXIT, "%s - done", __func__);
}

SmlBool smlSessionReceiveBody(SmlSession *session, SmlParser *parser, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, parser, error);
	smlAssert(session);
	smlAssert(parser);
	CHECK_ERROR_REF
	SmlStatus *status = NULL;
	SmlCommand *cmd = NULL;
	SmlParserResult result = SML_PARSER_RESULT_ERROR;
	SmlBool isSan = TRUE;

	if (session->sessionType == SML_SESSION_TYPE_SERVER)
		isSan = FALSE;

	/* The session received some stuff and so it is no longer waiting.
	 * It is important to set the state here because if it is set later
	 * then there is the risk that some dispatched command sends the
	 * next message via a direct flush. In this case a late state change
	 * could lead to a race condition because the correct state WAITING
	 * would be overwritten with the old value.
	 */
	session->waiting = FALSE;
	session->sending = FALSE;
	
parse_status:
	/* Now lets go through the available statuses (if any) */
	while (1) {
		if (!smlParserGetStatus(parser, &status, error))
			goto error_free_status;

		if (!status)
			break;

		/* There is at minimum one status. This means that
		 * this is not the first message and there is an
		 * header reply.
		 */
		isSan = FALSE;

		/* If there is a status then it is necessary to check
		 * the session environment before the user has a chance
		 * to do something with the received information.
		 *
		 * The background is that libsyncml does not handle the
		 * SANs in a SmlSession. Therefore the SmlSession is not
		 * aware of an already used MsgRef 1.
		 *
		 * If a status is received and lastMessageID is still 1
		 * then a reply to a SAN is assumed and the lastMessageID
		 * is fixed accordingly.
		 */
		if (session->lastMessageID == 1)
		{
			/* This should be an answer to a SAN. */
			smlTrace(TRACE_INTERNAL, "%s - reply to SAN detected", __func__);
			session->lastMessageID = 2;
		}

		/* Call the callback*/
		if (!smlSessionDispatchStatus(session, status, error))
			goto error_free_status;

		smlStatusUnref(status);
	}
	
	/* Go through the remaining commands */
	while (1) {
		result = smlParserGetCommand(parser, &cmd, error);
		if (result == SML_PARSER_RESULT_ERROR)
			goto error_free_cmd;
		if (result == SML_PARSER_RESULT_OTHER)
			break;
		
		if (cmd)
			cmd->msgID = session->lastReceivedMessageID;

		/* If the handler of the header signals that an authentication
		 * is still required then we must send status 407 to all
		 * commands. The client must send all commands with the
		 * next message again.
		 */
		if (!session->established && !isSan) {
			session->lastCommandID++;

			/* Reserve the place for the status in the assembler */
			if (!smlAssemblerReserveStatus(session->assembler, cmd->cmdID, session->lastReceivedMessageID, session->lastCommandID, error))
				goto error_free_cmd;

			/* send status 407 */
			SmlStatus *reply = smlCommandNewReply(
						cmd, SML_ERROR_AUTH_REQUIRED, error);
			if (!reply)
				goto error_free_cmd;
			if (!smlSessionSendReply(session, reply, error))
				goto error_free_cmd;
			smlStatusUnref(reply);
			smlCommandUnref(cmd);
			continue;
		}

		switch (result) {
			case SML_PARSER_RESULT_OPEN:
				session->lastCommandID++;
				/* Reserve the place for the status in the assembler */
				if (!smlAssemblerReserveStatus(session->assembler, cmd->cmdID, session->lastReceivedMessageID, session->lastCommandID, error))
					goto error_free_cmd;
				
				/* Store the parent */
				if (session->parentCommand) {
					smlErrorSet(error, SML_ERROR_GENERIC, "Already had a parent");
					goto error_free_cmd;
				}
				
				/* If we are a server, we just mimick the behaviour of the client regarding
				 * large object handling and support of number of changes. This way we dont
				 * need to parse the devinf */
				if (cmd->type == SML_COMMAND_TYPE_SYNC) {
					smlTrace(TRACE_INTERNAL, "%s: maxObjSize: %i, numbOfChanges: %i, %i",
						__func__,
						cmd->private.sync.maxObjSize,
						cmd->private.sync.hasNumChanged,
						cmd->private.sync.numChanged);
					/* If the sync has the maxObjSize set (>= 0), we set the requested max obj size.
					 * If the max obj size was set before (with the alert, we overwrite it */
					if (cmd->private.sync.maxObjSize >= 0)
						smlSessionSetRemoteMaxObjSize(session, cmd->private.sync.maxObjSize);
					/* If the DevInf of the client includes SupportNumberOfChanges
					 * but the client does not send NumberOfChanges
					 * then the server still MUST send NumberOfChanges.
					 * The client only MAY send NumberOfChanges.
					 *
					 * So even if NumberOfChanges are missing then never touch 
					 * the requested and configured NumberOfChanges property.
					 *
					 * Please see OMA DS Protocol v1.2
					 * Section 9   Two-Way Sync
					 * Section 9.1 Client Modifications to Server
					 * Section 9.2 Server Modifications to Client
					 */
				}

				session->parentCommand = cmd;
				cmd->msgID = session->lastReceivedMessageID;
				
				if (!session->end) {
					smlSessionDispatchEvent(session, SML_SESSION_EVENT_COMMAND_START, cmd, NULL, NULL, NULL);
				} else {
					smlTrace(TRACE_INTERNAL, "%s: Replying with 407 since the session has ended",
						__func__);
					SmlStatus *reply = smlCommandNewReply(cmd, SML_ERROR_AUTH_REQUIRED, error);
					if (!reply)
						goto error_free_cmd;
					
					if (!smlSessionSendReply(session, reply, error))
						goto error_free_cmd;
						
					smlStatusUnref(reply);
				}
				break;
			case SML_PARSER_RESULT_NORMAL:
				session->lastCommandID++;
				/* Reserve the place for the status in the assembler */
				if (!smlAssemblerReserveStatus(session->assembler, cmd->cmdID, session->lastReceivedMessageID, session->lastCommandID, error))
					goto error_free_cmd;
					
				/* Here we catch Alerts for the next message */
				if (cmd->type == SML_COMMAND_TYPE_ALERT) {
					if (cmd->private.alert.type == SML_ALERT_NEXT_MESSAGE) {
						SmlStatus *reply = smlCommandNewReply(cmd, SML_NO_ERROR, error);
						if (!reply)
							goto error_free_cmd;
						
						if (!smlSessionSendReply(session, reply, error))
							goto error_free_cmd;
							
						smlStatusUnref(reply);
						
						smlCommandUnref(cmd);
						break;
					} else {
						smlTrace(TRACE_INTERNAL, "%s: Alert maxObjSize: %i",
							__func__, cmd->private.alert.maxObjSize);
						if (cmd->private.alert.maxObjSize >= 0)
							smlAssemblerSetRemoteMaxObjSize(session->assembler, cmd->private.alert.maxObjSize);
					}
				}
				
				/* Here we catch changes with moreData set */
				if (cmd->type == SML_COMMAND_TYPE_ADD || cmd->type == SML_COMMAND_TYPE_REPLACE) {

					SmlItem *lastItem = g_list_nth_data(cmd->private.change.items,
								 g_list_length(cmd->private.change.items) - 1);

					if (session->incomingBuffer) {
						/* There is a command in the incoming buffer.
						 * This requires that all items of the received
						 * command are merged into this incoming buffer.
						 * Please remember that one command can contain
						 * several item each with a large object inside.
						 *
						 * If the last item of the command has no moreData
						 * flag then the command is dispatched.
						 */
						smlTrace(TRACE_INTERNAL, "%s: Appending to incoming buffer", __func__);

						/* The size of an item must not be set in the
						 * different chunks except the first one.
						 */
						if (cmd->size) {
							smlErrorSet(error, SML_ERROR_GENERIC,
								"Only the first chunk can have the size set");
							goto error_free_cmd;
						}

						/* Copy the item data of the first new item.
						 * This includes the moreData flag.
						 * This is a chunk of the previous item in the buffer.
						 */
						char *data = NULL;
						unsigned int size = 0;
						SmlItem *item = g_list_nth_data(cmd->private.change.items, 0);
						if (!item)
							goto error_free_cmd;
						if (!smlItemGetData(item, &data, &size, error))
							goto error_free_cmd;
						lastItem = g_list_nth_data(session->incomingBuffer->private.change.items, g_list_length(session->incomingBuffer->private.change.items) - 1);
						if (!smlItemAddData(lastItem, data, size, error))
							goto error_free_cmd;
						lastItem->moreData = item->moreData;
						smlTrace(TRACE_INTERNAL,
							"%s: Appended %i to buffer. Buffer size: %i. Required: %i",
							__func__, size,
							xmlBufferLength(lastItem->buffer),
							lastItem->size);

						/* move all other items from the new to the buffered command */
						guint i;
						for (i=1; i < g_list_length(cmd->private.change.items); i++)
						{
							item = g_list_nth_data(cmd->private.change.items, i);
							session->incomingBuffer->private.change.items =
								g_list_append(
									session->incomingBuffer->private.change.items,
									item);
						}
						smlItemUnref(g_list_nth_data(cmd->private.change.items, 0));
						g_list_free(cmd->private.change.items);
						cmd->private.change.items = NULL;

						/* If the last item of the command is complete
						 * then the command must be prepared for dispatching.
						 */
						lastItem = g_list_nth_data(session->incomingBuffer->private.change.items, g_list_length(session->incomingBuffer->private.change.items) - 1);
						if (!lastItem->moreData) {
							smlTrace(TRACE_INTERNAL,
								"%s: Command buffer complete. Dispatching.",
								__func__);

							/* check if the item size is correct */
							if (!smlItemCheck(lastItem)) {
								/* We have a size mismatch */
								SmlStatus *reply = smlCommandNewReply(cmd, SML_ERROR_SIZE_MISMATCH, error);
								if (!reply)
									goto error_free_cmd;
								if (!smlSessionSendReply(session, reply, error))
									goto error_free_cmd;
								smlStatusUnref(reply);
								smlErrorSet(error, SML_ERROR_GENERIC,
									"Chunked item size mismatch");
								goto error_free_cmd;
							}
							
							/* The original command is no longer needed.
							 * The incoming buffer cannot be used
							 * because several status informations like
							 * the message ID are required for correct
							 * status replies.
							 */
							cmd->private.change.items = 
								session->incomingBuffer->private.change.items;
							session->incomingBuffer->private.change.items = NULL;
							smlCommandUnref(session->incomingBuffer);
							session->incomingBuffer = NULL;
						}
					} else if (lastItem->moreData) {
						/* There is no buffered command yet.
						 * The new command is copied to the buffer.
						 * This is done by reference copy and
						 * an incrementation of the reference counter.
						 */

						if (!cmd->size) {
							smlErrorSet(error, SML_ERROR_GENERIC, "First MoreData item didn't have size set");
							goto error_free_cmd;
						}

						session->incomingBuffer = cmd;
						smlCommandRef(cmd);
						smlTrace(TRACE_INTERNAL, "%s: New Buffer started. Buffered item size: %i. Required: %i",
							__func__,
							xmlBufferLength(lastItem->buffer),
							lastItem->size);
						
						char *bin = smlPrintBinary((char *) xmlBufferContent(lastItem->buffer), xmlBufferLength(lastItem->buffer));
						smlTrace(TRACE_INTERNAL, "%s: Content so far: %s\n",
							__func__, VA_STRING(bin));
						smlSafeCFree(&bin);
					}
					
					if (lastItem->moreData == TRUE) {
						smlTrace(TRACE_INTERNAL, "%s: Got item with moreData %i",
							__func__, cmd->msgID);
						
						SmlStatus *reply = smlCommandNewReply(cmd, SML_CHUNK_ACCEPTED, error);
						if (!reply)
							goto error_free_cmd;
						
						if (!smlSessionSendReply(session, reply, error))
							goto error_free_cmd;
							
						smlStatusUnref(reply);
						
						smlCommandUnref(cmd);
						break;
					}
				}
				
				/* if the command is a result, we treat it as a status */
				if (cmd->type == SML_COMMAND_TYPE_RESULTS) {
					/* Call the callback*/
					if (!smlSessionDispatchStatus(session, cmd->private.results.status, error))
						goto error_free_status;
				} else {
					/* Dispatch the command */
					cmd->msgID = session->lastReceivedMessageID;
					if (!session->end) {
						if (!session->parentCommand) {
							smlSessionDispatchEvent(session, SML_SESSION_EVENT_COMMAND_START, cmd, NULL, NULL, NULL);
						} else {
							smlSessionDispatchEvent(session, SML_SESSION_EVENT_CHILD_COMMAND, cmd, session->parentCommand, NULL, NULL);
						}
					} else {
						smlTrace(TRACE_INTERNAL, "%s: Replying with 407 since the session has ended2",
							__func__);
						SmlStatus *reply = smlCommandNewReply(cmd, SML_ERROR_AUTH_REQUIRED, error);
						if (!reply)
							goto error_free_cmd;
						
						if (!smlSessionSendReply(session, reply, error))
							goto error_free_cmd;
							
						smlStatusUnref(reply);
					}
				}
				
				smlCommandUnref(cmd);
				break;
			case SML_PARSER_RESULT_CLOSE:
				/* If it is a sync then it is necessary to signal it twice
				 * because DsSession has an enabled write_lock.
				 * If you add other command types here then please check
				 * twice that the callback are ready to be called twice
				 * (usually this requires some kind of statefulness).
				 */
				if (session->parentCommand->type == SML_COMMAND_TYPE_SYNC) {
					smlSessionDispatchEvent(
						session,
						SML_SESSION_EVENT_COMMAND_END,
						session->parentCommand,
						NULL, NULL, NULL);
				}
				/* Remove the parent */
				smlCommandUnref(session->parentCommand);
				session->parentCommand = NULL;
				break;
			case SML_PARSER_RESULT_STATUS:
				goto parse_status;
			default:
				goto error_free_cmd;
		}
	}
	
	/* Check that the message closes correctly
	 * A final tag is only send if the SyncML package is finished.
	 * A package can consists of several messages if MaxMsgSize
	 * is smaller then the exchange data.
	 */
	SmlBool final = FALSE;
	SmlBool end = FALSE;
	if (!smlParserEnd(parser, &final, &end, error))
		goto error_free_cmd;

	/* Dispatch the final event before we enter the following loop. Otherwise if upper
	   layers depend on the final event we might end in a deadlock. */
	if (final && !session->authenticate) {
		/* If the session is handling an authentication
		 * then this must be transparent for the user.
		 * This means that the user should not see
		 * the FINAL event.
		 *
		 * Example: If the user flushs on FINAL then a FINAL
		 *          signal could crash the complete client
		 *          logic.
		 */
		/* FIXME: Why do we activate session->sending here? */
		session->sending = TRUE;
		smlSessionDispatchEvent(session, SML_SESSION_EVENT_FINAL, NULL, NULL, NULL, NULL);
	}
	
	/* We have to wait at this point until no status are pending anymore
	 * if there is an incoming buffer for large objs open,
	 * the other side expects to see the status for the open buffer of course. so we have to
	 * wait here for all open statuses, since they have to be added in the correct order and
	 * the status for the open buffer is obviously last */	
	while (1) {
		unsigned int cmdRef, msgRef;
		if (!smlAssemblerGetNextCmdRef(session->assembler, &cmdRef, &msgRef))
			break;
		smlTrace(TRACE_INTERNAL, "%s: command %d of message %d found",
			__func__, cmdRef, msgRef);

		/* yes, we do some kind busy wait here */
		usleep(10000);
		
		/* if there is nothing in the queue (num == 0) then this means
		 * that all status and commands are dispatched but the result of one or
		 * more triggered activities is still not available
		 * (therefore smlAssemblerGetNextCmdRef returns a command).
		 *
		 * smlQueueLengthPrio returns missing status count
		 */
		unsigned int num = smlQueueLengthPrio(session->command_queue);
		smlTrace(TRACE_INTERNAL, "%s: Now dispatching %i statuses. Session waiting: %i, prio: %i",
			__func__, num, session->waiting, smlQueueCheckPrio(session->command_queue));

		/* If the session does not wait for the remote peer (session->waiting)
		 * and there is a missing status in the queue
		 * then the message is dispatched again.
		 * The message is dispatched at maximimum once per missing status (i < num).
		 * If smlSessionDispatch blocks because the dispatch_mutex is already in use 
		 * and the situation changes (e.g. session->waiting is true)
		 * then this is no problem because the function is safe against
		 * mutliple calls (the SmlManager already requires this).
		 */
		unsigned int i = 0;
		while (!session->waiting && smlQueueCheckPrio(session->command_queue) && i < num) {
			smlSessionDispatch(session);
			i++;
		}
		/* should we really call dispatch here or wait? */
	}

	/* A lot of checks are performed before the while loop is executed.
	 * This is necessary because this while loop is only present for one
	 * reason, a NEXT MESSAGE request from the remote peer if the original
	 * message data does not fit into one message (MaxMsgSize to small).
	 * The following stuff is checked:
	 *
	 *     - the session must be in normal mode
	 *         - not sending or waiting
	 *         - no changed message counters
	 *     - there must be a waiting command (not a waiting status!)
	 *
	 * Usually this happens if an image is too large and must be splitted
	 * (MaxMsgSize < MaxObjSize). Do not touch this stuff except that you
	 * really test it with an image which must be send and it is correctly
	 * splitted.
	 */
	unsigned int receivedMsg = session->lastReceivedMessageID;
	unsigned int lastMsg = session->lastMessageID;
	while (!session->waiting && !session->sending &&
	       smlQueueCheck(session->command_queue) &&
	       smlQueueLength(session->command_queue) != smlQueueLengthPrio(session->command_queue) &&
	       receivedMsg == session->lastReceivedMessageID &&
	       lastMsg == session->lastMessageID)
	{
		/* If the session does not wait for the remote peer (session->waiting)
		 * and there is a command in the queue
		 * then the command is dispatched.
		 * If the command is too large then the session is automatically
		 * flushed. If all commands were dispatched then the queue check
		 * fails and the normal mechanism for sending messages flushes
		 * the session or do some other actions.
		 */
		smlQueueDispatch(session->command_queue);
	}

	smlTrace(TRACE_INTERNAL, "%s: Final %i, waiting %i, sending %i",
		__func__, final, session->waiting, session->sending);
	if (session->authenticate) {
		/* Do nothing. Simply send the message again.
		 * Please note that final and end MUST be ignored.
		 */
		smlTrace(TRACE_INTERNAL, "%s: ongoing authentication", __func__);
	}
	else if (final) {
		/* Final means that the received message is the last message
		 * of this SyncML package. When a final is received then the
		 * library should not flush the session automatically because
		 * the user of the library usually starts some special action.
		 *
		 * This can be for example a new package or some kind of
		 * background action which results in informations expected by
		 * the remote peer in the next message.
		 * Example: OMA DS client (remote peer) sends a SYNC package
		 *          and expects that the OMA DS server sends the server
		 *          modifications at minimum after the last client
		 *          modification.
		 *
		 * So again the library user must flush the session (e.g. handle
		 * the SML_MANAGER_SESSION_FINAL event).
		 */
		if (session->pendingReplies) {
			/* If final is reached then there should be no
			 * pendingReplies anymore. The problem is that some
			 * commands (especially SYNC) results in new commands.
			 * Nevertheless we have to check for old pendingReplies
			 * and this we try to do here.
			 *
			 * Yes, this is some kind of bug detection.
			 */
			GList *o = NULL;
        		for (o = session->pendingReplies; o; o = o->next) {
                		SmlPendingStatus *pending = o->data;
				/* This is an asynchronous library but the
				 * message numbering and  sending is a serial
				 * operation. So pendingReplies should only
				 * exist for the last message. Anything else
				 * should be a bug.
				 * The problem is that a race condition can
				 * appear here. If the user of the library
				 * gets the FINAL event and flushs the session
				 * immediately then the flush function will
				 * send the message and increment the counter
				 * lastMessageID. So lastMessageID is in this
				 * case one number too high. The solution is to
				 * detect this event by the state of the
				 * session via the status variable waiting.
				 */
                		if ((!session->waiting && pending->msgID < session->lastMessageID) ||
				      (session->waiting && pending->msgID < session->lastMessageID - 1))
				{
					smlErrorSet(error, SML_ERROR_GENERIC, "Didn't receive a reply for pending status (actual message %i, received message %i, cmdID %i, msgID %i)", session->lastMessageID, session->lastReceivedMessageID, pending->cmdID, pending->msgID);
					goto error;
				}
                	}
		}

		/* The incomingBuffer is used to store the last unhandled item.
		 * The incoming buffer can be filled after a message is
		 * completely handled if an object is too large for a message.
		 * Usually this happens if you add images to your contacts ;)
		 * The remote peer sends the moreData element in this case.
		 *
		 * Nevertheless if final is received then the package is
		 * finished and so there cannot be any more data which is not
		 * send by the client.
		 *
		 * Summary: there is a bug in this library or in the remote peer.
		 */
		if (session->incomingBuffer) {
			smlErrorSet(error, SML_ERROR_GENERIC, "Incoming buffer was open when final arrived");
			smlCommandUnref(session->incomingBuffer);
			session->incomingBuffer = NULL;
			goto error;
		}
	} else if (!session->waiting && !session->sending) {
		/* If we don't wait already for an answer from the remote peer
		 * (perhaps because a user of this library directly called
		 * smlSessionFlush which causes a send operation of the
		 * transport layer) and if we are waiting for or already
		 * receiving a package (in terms of OMA DS specifications)
		 * then we should send the collected stuff to the remote peer.
		 */
		smlTrace(TRACE_INTERNAL, "%s: This is not the final message. Empty? %i",
			__func__, smlAssemblerIsEmpty(session->assembler));
		/* The received message was not a final message. So this is
		 * only a reaction to a part from a package. Sometimes such
		 * replies only contain status but a message must include a
		 * command.
		 */
		if (smlAssemblerIsEmpty(session->assembler)) {
			/* The assembler is empty. This means there is no
			 * command included which is a requirement.
			 * The solution is simple - an alert called next
			 * message is added. this special alert does not need
			 * a source or target because it only signals that the
			 * remote peer should continue its work.
			 */
			SmlCommand *alert = smlCommandNewAlert(SML_ALERT_NEXT_MESSAGE, NULL, NULL, NULL, NULL, NULL, error);
			if (!alert)
				goto error;

			if (_smlSessionStartCommandInternal(session, alert, NULL, NULL, NULL, TRUE, error) != SML_ASSEMBLER_RESULT_OK)
				goto error;
			if (!_smlSessionEndCommandInternal(session, NULL, error))
				goto error;

			smlCommandUnref(alert);
		}
		/* Anything is fine and nobody else want to send a reply
		 * to the received message. So the library flushs now.
		 */
		if (!_smlSessionFlushInternal(session, FALSE, error))
			goto error;
	}

	if (session->authenticate) {
		/* Do nothing. Simply send the message again. */
		smlTrace(TRACE_INTERNAL, "%s: ongoing authentication", __func__);
		/* session->end signals true because an authentication request
		 * is signaled via status/error 407 as answer to all commands.
		 */
		session->end = FALSE;
		smlTrace(TRACE_INTERNAL, "%s: flushing server finally",
			__func__);
		if (!_smlSessionFlushInternal(session, TRUE, error))
			goto error;
		/* reset authenticate only after flush
		 * because we must surpress the FLUSH event.
		 */
		session->authenticate = FALSE;
	} else if (end) {
		/* end means the last message does not contain a command which
		 * is clearly illegal in SyncML except that the remote party
		 * thinks that the device synchronization or management action
		 * is successfully completed.
		 *
		 * The library can close the session now.
		 */
		smlTrace(TRACE_INTERNAL, "%s: Ending session now", __func__);
		session->end = TRUE;

		/* If the OMA DS or DM session ends and a correct FINAL was
		 * received then clients should simply end and servers should do
		 * a final flush according to OMA DS spec.
		 *
		 * This only makes sense after a received FINAL because
		 * otherwise the session was already flushed in some way. This
		 * can happen if the user of the library takes action because of
		 * an event.
		 */
		if (final)
		{
			smlTrace(TRACE_INTERNAL, "%s: session end on final",
				__func__);
			if (session->sessionType == SML_SESSION_TYPE_SERVER)
			{
				smlTrace(TRACE_INTERNAL, "%s: flushing server finally",
					__func__);
				if (!_smlSessionFlushInternal(session, TRUE, error))
					goto error;
			}
		} else {
			if (!session->waiting)
			{
				/* The library does not send a message
				 * after it received the last message
				 * and neither the remote peer nor the
				 * library send a final. This means
				 * there is an active package. This is
				 * an error of the library or the remote
				 * peer.
				 */
				smlErrorSet(error, SML_ERROR_BAD_REQUEST,
					"The message from the remote peer does not contain a command.");
				goto error;
			}
		}
		/* Send the library user the signal that the session ends now
		 * and every use of this session will result in an undefined
		 * behaviour.
		 * If this is a server which received a final
		 * then FlushInternal already signalled the session end.
		 */
		if (!final || session->sessionType != SML_SESSION_TYPE_SERVER)
		{
			g_mutex_lock(session->reportEnd);
			if (!session->reportedEnd) {
				/* The end should only be reported once. */
				session->reportedEnd = TRUE;
				smlSessionRestoreTargetURI(session);
				smlSessionDispatchEvent(session, SML_SESSION_EVENT_END,
					 NULL, NULL, NULL, NULL);
			}
			g_mutex_unlock(session->reportEnd);
		}
	}

	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
	
error_free_cmd:
	if (cmd)
		smlCommandUnref(cmd);
error_free_status:
	if (status)
		smlStatusUnref(status);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

void smlSessionSetEventCallback(SmlSession *session, SmlSessionEventCallback callback, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, callback, userdata);
	smlAssert(session);
	
	session->eventCallback = callback;
	session->eventCallbackUserdata = userdata;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void smlSessionSetDataCallback(SmlSession *session, SmlSessionDataCallback callback, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, callback, userdata);
	smlAssert(session);
	
	session->dataCallback = callback;
	session->dataCallbackUserdata = userdata;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

const char *smlSessionGetSessionID(SmlSession *session)
{
	smlAssert(session);
	
	return session->sessionID;
}

void smlSessionSetSessionID(SmlSession *session, const char *sessionID)
{
	smlAssert(session);
	
	if (session->sessionID)
		smlSafeCFree(&(session->sessionID));
	session->sessionID = g_strdup(sessionID);
}

SmlProtocolVersion smlSessionGetVersion(SmlSession *session)
{
	smlAssert(session);
	
	return session->version;
}

SmlLocation *smlSessionGetSource(SmlSession *session)
{
	smlAssert(session);
	return session->source;
}

SmlLocation *smlSessionGetTarget(SmlSession *session)
{
	smlAssert(session);
	return session->target;
}

void smlSessionRegisterCred(SmlSession *session, SmlCred *cred)
{
	smlAssert(session);
	smlAssert(session->sessionType == SML_SESSION_TYPE_CLIENT);
	smlAssert(cred);
	if (session->cred)
		smlCredUnref(session->cred);
	session->cred = cred;
	if (cred->username)
		smlLocationSetName(session->source, cred->username);
	smlCredRef(session->cred);
}

/*@}*/

/**
 * @defgroup SmlSession SyncML Session
 * @ingroup PublicLowLevelAPI
 * @brief Interfaces to create, manipulate and delete sessions
 * 
 * A session has a unique id and consists of several packages which again contain several commands.
 * 
 */
/*@{*/

/**
 * @name Session Management
 * These function allow to create and delete sessions
 */
/*@{*/

/** @brief Creates a new session
 * 
 * Note that if you set the new session callback on the transport, you will
 * get a callback that this session was created (so that it gets easier to register
 * objects at a central place)
 * 
 * @param sessionType If the session is a server or a client.
 * @param mimetype The desired mime type. Choose if you want xml or wbxml
 * @param version What version of the protocol do you want to use?
 * @param protocol Which syncml protocol do you want to use?
 * @param target The remote target of this session. This is used in the header
 * @param source The source of this session. Used in the header
 * @param sessionID If you already have a sessionID, you can enter it here. Set it to NULL if you want to generate a new ID
 * @param messageID The message Id where this session should start. set to 0 to take the default
 * @param error A pointer to a error struct
 * @returns The new session or NULL if an error occured
 * 
 */
SmlSession *smlSessionNew(SmlSessionType sessionType, SmlMimeType mimetype, SmlProtocolVersion version, SmlProtocolType protocol, SmlLocation *target, SmlLocation *source, const char *sessionID, unsigned int messageID, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%i, %i, %i, %i, %p, %p, %s, %i, %p)", __func__, sessionType, mimetype, version, protocol, target, source, VA_STRING(sessionID), messageID, error);
	CHECK_ERROR_REF

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

	/* Malloc the new session object */
	SmlSession *session = smlTryMalloc0(sizeof(SmlSession), error);
	if (!session)
		goto error;

	session->dispatch_mutex = g_mutex_new();
	session->refCount = 1;
	session->eventCallback = NULL;
	session->version = version;
	session->protocol = protocol;
	session->type = mimetype;
	session->sessionID = g_strdup(sessionID);
	if (messageID)
		session->lastMessageID = messageID;
	else
		session->lastMessageID = 1;
	
	session->end = FALSE;
	session->reportEnd = g_mutex_new();
	session->reportedEnd = FALSE;
	session->sessionType = sessionType;
	session->lastCommandID = 0;

	if (sessionType == SML_SESSION_TYPE_CLIENT)
		session->sending = TRUE;
	else
		session->sending = FALSE;
	
	session->source = source;
	smlLocationRef(source);
		
	session->target = target;
	smlLocationRef(target);

	session->cred = NULL;
	session->chal = NULL;
	
	/* Create the assembler that will parse messages send
	 * from this session */
	session->assembler = smlAssemblerNew(mimetype, 0, error);
	if (!session->assembler)
		goto error_free_session;
	smlAssemblerSetOption(session->assembler, "USE_STRTABLE", "1");
	
	if (!smlAssemblerStart(session->assembler, session, error))
		goto error_free_session;
	
	/* Do not call smlAssemblerAddHeader here. It is nice to know
	 * if the header is is too large for the maximum message size
	 * but there is sometimes not enough information to pass all
	 * sanity checks of the assembler at this time (e.g. missing
	 * local MaxMsgSize).
	 */
	session->assmHasHeader = FALSE;
	
	session->command_queue = smlQueueNew(error);
	if (!session->command_queue)
		goto error_free_session;
	smlQueueSetHandler(session->command_queue, (SmlQueueHandler)_smlSessionCommandHandler, session);
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, session);
	return session;

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

SmlSession *smlSessionRef(SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, session);
	smlAssert(session);
	
	g_atomic_int_inc(&(session->refCount));
	
	smlTrace(TRACE_EXIT, "%s: New refcount: %i", __func__, session->refCount);
	return session;
}

void smlSessionUnref(SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, session);
	smlAssert(session);
	
	if (g_atomic_int_dec_and_test(&(session->refCount))) {
		smlTrace(TRACE_INTERNAL, "%s: Refcount == 0!", __func__);
		
		if (session->target)
			smlLocationUnref(session->target);
		
		if (session->responseURI)
			smlSafeCFree(&(session->responseURI));

		if (session->source)
			smlLocationUnref(session->source);

		if (session->cred)
			smlCredUnref(session->cred);
		
		if (session->chal)
			smlChalUnref(session->chal);
		
		if (session->sessionID)
			smlSafeCFree(&(session->sessionID));
		
		if (session->command_queue) {
			SmlSessionCommand *sesscmd = NULL;
			while ((sesscmd = smlQueueTryPop(session->command_queue)))
				_smlSessionCommandFree(sesscmd);
			
			smlQueueFree(session->command_queue);
		}
		
		_smlSessionFreePendingReplies(session);
		
		if (session->assembler)
			smlAssemblerFree(session->assembler);

		if (session->incomingBuffer) {
			smlTrace(TRACE_INTERNAL, "%s: There is an unhandled incoming buffer.", __func__);
			smlCommandUnref(session->incomingBuffer);
		}
		
		if (session->parentCommand)
			smlCommandUnref(session->parentCommand);

		smlTrace(TRACE_INTERNAL, "%s: cleanup dispatch mutex", __func__);
		if (!g_mutex_trylock(session->dispatch_mutex))
			g_warning("%s: The session is still locked for dispatching - forcing unlock/free.",
				__func__);
		g_mutex_unlock(session->dispatch_mutex);
		g_mutex_free(session->dispatch_mutex);
		smlTrace(TRACE_INTERNAL, "%s: cleanup reportEnd mutex", __func__);
		/* It is no bug if reportEnd is not used. */
		if (!g_mutex_trylock(session->reportEnd))
			g_warning("%s: The reportEnd is still locked to set reportEnd - forcing unlock/free.",
				__func__);
		g_mutex_unlock(session->reportEnd);
		g_mutex_free(session->reportEnd);
		
		smlSafeFree((gpointer *)&session);
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

/*@}*/

/**
 * @name Session Control
 * These function allow to control a session
 */
/*@{*/

/** @brief Flushes a session
 * 
 * This flushes the already added statuses and/or commands. Note that if the session
 * has a size limitation, it will get flushed automatically if the size of the already
 * added commands exceeds the limit. Use this function if you want for some reason
 * to flush manually.
 * 
 * @param session The session to flush
 * @param final Set to TRUE if you want to have the final flag added. Otherwise it will just flush
 * the already added statuses and commands
 * @param error A error struct
 * @return TRUE if successful, FALSE otherwise
 * 
 */
SmlBool smlSessionFlush(SmlSession *session, SmlBool final, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p)", __func__, session, final, error);
	smlAssert(session);
	CHECK_ERROR_REF
	
	SmlSessionCommand *sesscmd = smlTryMalloc0(sizeof(SmlSessionCommand), error);
	if (!sesscmd)
		goto error;
	
	sesscmd->type = SML_SESSION_FLUSH;
	sesscmd->final = final;
	
	smlTrace(TRACE_INTERNAL, "%s: sending flush (%i) command %p via queue %p",
		__func__, SML_SESSION_FLUSH, sesscmd, session->command_queue);

	smlQueueSend(session->command_queue, sesscmd);

	// session is still active even if the other side sent only status
	session->end = FALSE;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

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

/** @brief Ends the session
 * 
 * This function will end the session. This means that no more commands will be sent.
 * If there are replies waiting they will be sent. if there are commands waiting from the
 * remote side which have not yet been answered, they will get a error reply. If you already
 * sent commands in this session, you will receive error replies for your callbacks.
 * 
 * If you registered a session end handler it will get called. if the session is a server type
 * and it receives a command from the remote side for the ended session libsyncml will
 * automatically send an error reply.
 * 
 * Sessions will be deleted from the server session after a timeout.
 * 
 * @param session The session to flush
 * @param error A error struct
 * @return TRUE if successful, FALSE otherwise
 * 
 */
SmlBool smlSessionEnd(SmlSession *session, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, session, error);
	smlAssert(session);
	CHECK_ERROR_REF

	g_mutex_lock(session->reportEnd);
	if (session->reportedEnd)
	{
		/* If the end was already reported
		 * then this function should not be called.
		 * This can happen because of race conditions
		 * during the event handling. So this event
		 * is not necessarily a bug.
		 */
		g_mutex_unlock(session->reportEnd);
		smlTrace(TRACE_EXIT, "%s - session end already signalled.", __func__);
		return TRUE;
	}
	session->reportedEnd = TRUE;

	/* If the session received all status and has no command ready
	 * to send then it makes no sense to flush. Such situations can
	 * appear if the user opens a connection to a SyncML server but
	 * after the connect the user notices that there is nothing to
	 * synchronize.
	 *
	 * The only exception is a server which received a map from the
	 * client as answer for a sync command. The server must send an
	 * empty message to the client. If the client sends no map
	 * then this is handled by the function ReceiveBody.
	 */
	if (smlSessionCheck(session) ||
	    (!session->end && session->lastCommandID))
	{
		/* The session end is reported by the internal
		 * flush function in this situation.
		 */
		session->reportedEnd = FALSE;

		SmlSessionCommand *sesscmd = smlTryMalloc0(sizeof(SmlSessionCommand), error);
		if (!sesscmd)
			goto error;

		sesscmd->type = SML_SESSION_FLUSH;
		sesscmd->final = TRUE;
		sesscmd->end = TRUE;
		smlTrace(TRACE_INTERNAL, "%s: sending command %p", sesscmd);

		smlQueueSend(session->command_queue, sesscmd);
	} else {
		/* If there is nothing to synchronize
		 * then simply signal the end.
		 */
		smlTrace(TRACE_INTERNAL, "%s: no status/command ... only sending event", __func__);
		smlSessionRestoreTargetURI(session);
		smlSessionDispatchEvent(session, SML_SESSION_EVENT_END, NULL, NULL, NULL, NULL);
	}

	g_mutex_unlock(session->reportEnd);
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error:
	g_mutex_unlock(session->reportEnd);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

/* This is for the client side of ResponseURI support. */
void smlSessionRestoreTargetURI(SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, session);
	smlAssert(session);

	if (session->orgTarget) {
		/* Restore original target if RespURI was used.
		 * This is unimportant because every new session
		 * must use a new link (connection).
		 */
		smlLocationUnref(session->target);
		session->target = session->orgTarget;
		session->orgTarget = NULL;
		/* Do not try to reset the ResponseURI via the session
		 * event because the original Target URI is very often
		 * no URL which causes an error in the transport layer.
		 */
	}

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

/* This is for the server side of ResponseURI support. */
SmlBool smlSessionSetResponseURI(
		SmlSession *session,
		const char *responseURI,
		SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %s, %p)", __func__, session, VA_STRING(responseURI), error);
	CHECK_ERROR_REF

	if (session->responseURI) {
		g_warning("The response URI (%s) of a session (%s) should never be overwritten (%s).",
			session->responseURI, session->sessionID, responseURI);
		smlSafeCFree(&(session->responseURI));
	}

	session->responseURI = g_strdup(responseURI);

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

void smlSessionUseStringTable(SmlSession *session, SmlBool useStringtable)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i)", __func__, session, useStringtable);
	smlAssert(session);
	
	smlAssemblerSetOption(session->assembler, "USE_STRTABLE", useStringtable ? "1" : "0");
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void smlSessionUseNumberOfChanges(SmlSession *session, SmlBool support)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i)", __func__, session, support);
	smlAssert(session);
	
	smlAssemblerSetOption(session->assembler, "USE_NUMBEROFCHANGES", support ? "1" : "0");
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void smlSessionUseLargeObjects(SmlSession *session, SmlBool support)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i)", __func__, session, support);
	smlAssert(session);
	
	smlAssemblerSetOption(session->assembler, "USE_LARGEOBJECTS", support ? "1" : "0");
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void smlSessionUseOnlyReplace(SmlSession *session, SmlBool onlyReplace)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i)", __func__, session, onlyReplace);
	smlAssert(session);
	
	smlAssemblerSetOption(session->assembler, "ONLY_REPLACE", onlyReplace ? "1" : "0");
	session->onlyReplace = onlyReplace;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

/* FIXME: DEPRECATED */
void smlSessionSetSendingLimit(SmlSession *session, unsigned int limit)
{
	smlSessionSetRemoteMaxMsgSize(session, limit);
}

/* FIXME: DEPRECATED */
unsigned int smlSessionGetSendingLimit(SmlSession *session)
{
	return smlSessionGetRemoteMaxMsgSize(session);
}

/* FIXME: DEPRECATED */
void smlSessionSetReceivingLimit(SmlSession *session, unsigned int limit)
{
	smlSessionSetLocalMaxMsgSize(session, limit);
}

/* FIXME: DEPRECATED */
void smlSessionSetSendingMaxMsgSize(SmlSession *session, unsigned int size)
{
	smlSessionSetRemoteMaxMsgSize(session, size);
}

/* FIXME: DEPRECATED */
unsigned int smlSessionGetSendingMaxMsgSize(SmlSession *session)
{
	return smlSessionGetRemoteMaxMsgSize(session);
}

/* FIXME: DEPRECATED */
void smlSessionSetReceivingMaxMsgSize(SmlSession *session, unsigned int size)
{
	smlSessionSetLocalMaxMsgSize(session, size);
}

void smlSessionSetRemoteMaxMsgSize(SmlSession *session, unsigned int size)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %u)", __func__, session, size);
	smlAssert(session);
	
	smlAssemblerSetRemoteMaxMsgSize(session->assembler, size);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

unsigned int smlSessionGetRemoteMaxMsgSize(SmlSession *session)
{
	smlAssert(session);
	return smlAssemblerGetRemoteMaxMsgSize(session->assembler);
}

void smlSessionSetLocalMaxMsgSize(SmlSession *session, unsigned int size)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %u)", __func__, session, size);
	smlAssert(session);

	if (! smlSessionGetRemoteMaxMsgSize(session) > 0 ||
	    smlSessionGetRemoteMaxMsgSize(session) > size)
	{
		session->localMaxMsgSize = size;
	} else {
		session->localMaxMsgSize = smlSessionGetRemoteMaxMsgSize(session);
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

unsigned int smlSessionGetLocalMaxMsgSize(SmlSession *session)
{
	smlAssert(session);
	return session->localMaxMsgSize;
}

/* FIXME: DEPRECATED */
void smlSessionSetSendingMaxObjSize(SmlSession *session, int limit)
{
	smlSessionSetRemoteMaxObjSize(session, limit);
}

/* FIXME: DEPRECATED */
void smlSessionSetRequestedMaxObjSize(SmlSession *session, int limit)
{
	smlSessionSetRemoteMaxObjSize(session, limit);
}

/* FIXME: DEPRECATED */
int smlSessionGetSendingMaxObjSize(SmlSession *session)
{
	return smlSessionGetRemoteMaxObjSize(session);
}

/* FIXME: DEPRECATED */
void smlSessionSetReceivingMaxObjSize(SmlSession *session, unsigned int limit)
{
	smlSessionSetLocalMaxObjSize(session, limit);
}

/* FIXME: DEPRECATED */
unsigned int smlSessionGetReceivingMaxObjSize(SmlSession *session)
{
	return smlSessionGetLocalMaxObjSize(session);
}

/** Sets the maximum obj size for the remote peer.
 * If we try to send an item to the remote
 * side which is larger than this limit we return an error 
 * 
 * Possible values for limit are
 * 
 * 0 means that we can send objects of any size
 * > 0 means that a limit is set
 * 
 * */
void smlSessionSetRemoteMaxObjSize(SmlSession *session, unsigned int limit)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i)", __func__, session, limit);
	smlAssert(session);
	
	smlAssemblerSetRemoteMaxObjSize(session->assembler, limit);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

/** Returns the maximum obj size that we can send. If both the sending and the
 * requested limit are set, this function will return the smaller one of both.
 * 
 * Otherwise it will return the larger number. */
unsigned int smlSessionGetRemoteMaxObjSize(SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, session);
	smlAssert(session);

	int result = smlAssemblerGetRemoteMaxObjSize(session->assembler);

	smlTrace(TRACE_EXIT, "%s => %i", __func__, result);
	return result;
}

/** Sets the obj size that will get transmitted to the remote side in the
 * <MaxObjSize> tag. So this sets the maximum size of the object we want to receive.
 * If an item is larger, we return an error 
 * 
 * In this function, 0 means that no limit is set.
 * a value > 0 means that a limit is set. note that this is required if large object
 * handling has to be supported.
 * 
 * The value is stored in the session since it has to be used by the assembler (to fill
 * the MaxObjSize tag) as well as by the session (to determine when to send an error if the
 * limit is violated)
 * 
 * */
void smlSessionSetLocalMaxObjSize(SmlSession *session, unsigned int limit)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %u)", __func__, session, limit);
	smlAssert(session);

	int remoteLimit = smlSessionGetRemoteMaxObjSize(session);

	if (remoteLimit > 0)
	{
		/* The remote peer already sent MaxObjSize */
		if ((unsigned int)remoteLimit > limit && limit > 0)
		{
			/* If the limit of the remote peer is higher
			 * than the local limit then the local limit
			 * must be used.
			 */
			session->localMaxObjSize = limit;
		} else {
			/* If the remote limit is not higher than
			 * the local limit or of the local limit is
			 * not set then the remote limit is used as
			 * the local limit.
			 */
			session->localMaxObjSize = remoteLimit;
		}
	} else {
		/* If there is no remote limit then the local limit is used. */
		session->localMaxObjSize = limit;
	}
	
	smlTrace(TRACE_EXIT, "%s => %d", __func__, session->localMaxObjSize);
}

unsigned int smlSessionGetLocalMaxObjSize(SmlSession *session)
{
	smlAssert(session);
	return session->localMaxObjSize;
}

/*@}*/

/** @brief Sends a command with a parent
 * 
 * This function can be used to send subcommands (for example a "Add" inside a "Sync"). You
 * are guaranteed that you will get a callback if this function returns TRUE. So either this function
 * returns FALSE (might occur if the command is not complete or if a OOM occured) or you will later
 * get a reply or an error in the callback.
 * 
 * Note that if you dont set a callback function, libsyncml will automatically add a "NoResp".
 * 
 * @param session The session
 * @param cmd The command to send
 * @param parent The parent of this command. NULL for syncbody
 * @param callback The callback that will receive the reply. Set to NULL if you dont want any replies.
 * @param userdata The userdata to pass to the callback
 * @param error A error struct
 * @return TRUE if successful, FALSE otherwise
 * 
 */
SmlBool smlSessionSendCommand(SmlSession *session, SmlCommand *cmd, SmlCommand *parent, SmlStatusReplyCb callback, void *userdata, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p, %p, %p)", __func__, session, cmd, parent, callback, userdata,  error);
	smlAssert(session);
	smlAssert(cmd);
	CHECK_ERROR_REF
	
	if (!smlSessionStartCommand(session, cmd, parent, callback, userdata, error))
		goto error;
		
	if (!smlSessionEndCommand(session, parent, error))
		goto error;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

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

/** @brief starts a command with a parent
 * 
 * @param session The session
 * @param cmd The command to send
 * @param parent The parent of this command
 * @param callback The callback that will receive the reply. Set to NULL if you dont want any replies.
 * @param userdata The userdata to pass to the callback
 * @param error A error struct
 * @return TRUE if successful, FALSE otherwise
 * 
 */
SmlBool smlSessionStartCommand(SmlSession *session, SmlCommand *cmd, SmlCommand *parent, SmlStatusReplyCb callback, void *userdata, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p, %p, %p)", __func__, session, cmd, parent, callback, userdata,  error);
	smlAssert(session);
	smlAssert(cmd);
	CHECK_ERROR_REF
	
	SmlSessionCommand *sesscmd = smlTryMalloc0(sizeof(SmlSessionCommand), error);
	if (!sesscmd)
		goto error;
	
	sesscmd->type = SML_SESSION_COMMAND_START;
	sesscmd->cmd = cmd;
	smlCommandRef(cmd);
	
	if (parent) {
		sesscmd->parent = parent;
		smlCommandRef(parent);
	}
	
	sesscmd->callback = callback;
	sesscmd->callbackUserdata = userdata;
	
	smlQueueSend(session->command_queue, sesscmd);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

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

/** @brief ends a command
 * 
 * @param session The session
 * @param parent The parent of this command, NULL for syncbody
 * @param error A error struct
 * @return TRUE if successful, FALSE otherwise
 * 
 */
SmlBool smlSessionEndCommand(SmlSession *session, SmlCommand *parent, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, parent, error);
	smlAssert(session);
	CHECK_ERROR_REF
	
	SmlSessionCommand *sesscmd = smlTryMalloc0(sizeof(SmlSessionCommand), error);
	if (!sesscmd)
		goto error;
	
	sesscmd->type = SML_SESSION_COMMAND_END;
	
	if (parent) {
		sesscmd->parent = parent;
		smlCommandRef(parent);
	}
	
	smlQueueSend(session->command_queue, sesscmd);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

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


/** @brief Sends a reply to a command
 * 
 * This function sends a status reply to a command.
 * 
 * This function might return an error in the case of an OOM situation or if you forgot to
 * fill in parts of the reply
 * 
 * @param session The session
 * @param status The status to send
 * @param error A error struct
 * @return TRUE if successful, FALSE otherwise
 * 
 */
SmlBool smlSessionSendReply(SmlSession *session, SmlStatus *status, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, status, error);
	smlAssert(session);
	smlAssert(status);
	CHECK_ERROR_REF
	
	SmlSessionCommand *sesscmd = smlTryMalloc0(sizeof(SmlSessionCommand), error);
	if (!sesscmd)
		goto error;
	
	sesscmd->type = SML_SESSION_STATUS;
	sesscmd->status = status;
	smlStatusRef(status);
	
	smlQueueSendPrio(session->command_queue, sesscmd);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

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

/*@}*/

/*@}*/
