/*
 * libsyncml - A syncml protocol implementation
 * Copyright (C) 2005  Armin Bauer <armin.bauer@opensync.org>
 * Copyright (C) 2007-2009  Michael Bell <michael.bell@opensync.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 */
 
#include "sml_session_internals.h"

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

#include "sml_queue_internals.h"
#include "sml_command.h"
#include "sml_elements.h"
#include "sml_parse.h"
#include "sml_transport.h"
#include "objects/sml_auth.h"

#include "data_sync_api/sml_location.h"
#include "data_sync_api/sml_data_sync_change_item_internals.h"

#include <unistd.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;
	gboolean final;
	gboolean end;
} SmlSessionCommand;

void
smlSessionDispatchEvent (SmlSession *session,
                         SmlSessionEventType type,
                         SmlCommand *cmd,
                         SmlCommand *parent,
                         SmlStatus *headerreply,
                         const GError *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 gboolean
_smlSessionFlushInternal (SmlSession *session,
                          gboolean final,
                          GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p)", __func__, session, final, error);
	CHECK_ERROR_REF
	smlAssert(session);
	smlAssert(session->dataCallback);
	
	char *data = NULL;
	gsize size = 0;
	gboolean 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;
	data = NULL;
	
	if (end && session->sessionType == SML_SESSION_TYPE_SERVER)
		smlTransportDataSetNeedsAnswer(tspdata, 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;
	session->msgChanges = 0;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
error:
	if (data)
		smlSafeCFree(&data);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	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__, smlStatusGetType(status));
	
	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,
                         gsize space,
                         gsize start,
                         GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %i, %i, %p)", __func__, session, orig_cmd, parent, space, start, error);
	CHECK_ERROR_REF
	/* This library must not send more than one item per change
	 * command. This is compliant with all specification and only a
	 * self limitation.
	 */
	smlAssert(smlCommandGetNumChanges(orig_cmd) == 1);
	SmlCommand *frag_cmd = NULL;
	
	gsize size = smlCommandGetNthItemSize(orig_cmd, 0, error);
	if (!size)
		goto error;

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

		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.
		 */

		/* create fragmentation command */
		frag_cmd = smlCommandGetFragment(orig_cmd, start, size - start, error);
		if (!frag_cmd)
			goto error;

		/* setup fragmentation command */
		session->frag_size += size - start;
	}
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, frag_cmd);
	return frag_cmd;
error:
	if (frag_cmd)
		smlCommandUnref(frag_cmd);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

static SmlAssemblerResult
_smlSessionStartCommandInternal (SmlSession *session,
                                 SmlCommand *cmd,
                                 SmlCommand *parent,
                                 SmlStatusReplyCb callback,
                                 void *userdata,
                                 gboolean addToCommandStack,
                                 GError **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;
	gboolean fragmented = FALSE;

	/* If the user specified a maximum number of changes per message
	 * then this is the place to flush the message.
	 */

	if (smlCommandGetType(cmd) == SML_COMMAND_TYPE_ADD ||
	    smlCommandGetType(cmd) == SML_COMMAND_TYPE_REPLACE ||
	    smlCommandGetType(cmd) == SML_COMMAND_TYPE_DELETE)
		session->msgChanges++;

	if (0 < session->localMaxMsgChanges &&
	    session->localMaxMsgChanges < session->msgChanges)
	{
		smlTrace(TRACE_INTERNAL, "%s: maximum number of changes per message exceeded", __func__);
		if (!_smlSessionFlushInternal(session, FALSE, error)) {
			goto error;
		}
	}
	
	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 */
	smlTrace(TRACE_INTERNAL, "%s: type: %d, MaxMsgSize: %llu, version: %d",
	         __func__,
		 smlCommandGetType(cmd),
		 smlAssemblerGetRemoteMaxMsgSize(session->assembler),
		 session->version);
	if ((smlCommandGetType(cmd) == SML_COMMAND_TYPE_ADD ||
	     smlCommandGetType(cmd) == 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);
		
		SmlDataSyncChangeItem *item = smlCommandGetNthChange(cmd, 0);
		smlAssert(sml_data_sync_change_item_get_data(item));
		gsize size = strlen(sml_data_sync_change_item_get_data(item));
		
		/* 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 */
		gsize sendingmaxobjsize = smlSessionGetRemoteMaxObjSize(session);
		if ((sendingmaxobjsize > 0) &&
		    (size > sendingmaxobjsize))
		{
			g_set_error(error, SML_ERROR, SML_ERROR_SIZE_MISMATCH,
			            "Item (%ub) is larger than the limit (%db)", size, smlSessionGetRemoteMaxObjSize(session));
			goto error;
		}
		
		if (!session->frag_command)
			smlCommandSetSize(cmd, size);
		sml_data_sync_change_item_set_missing_data(item, TRUE);
		gssize space = 0;
		if (!smlAssemblerGetSpace(session->assembler, &space, parent, cmd, error))
			goto error;
		smlCommandSetSize(cmd, 0);
		sml_data_sync_change_item_set_missing_data(item, 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, 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 = smlCommandGetNthChange(cmd, 0);
			if (sml_data_sync_change_item_get_missing_data(item) == 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);
		}
	}

	/* A Map command cannot be splitted like an Add or Change command.
	 * A map can be splitted into several maps if no Final is sent.
	 * Maps can be splitted for every SyncML protocol version.
	 * This is no large object handling.
	 */
	if (smlCommandGetType(cmd) == SML_COMMAND_TYPE_MAP &&
	    smlAssemblerGetRemoteMaxMsgSize(session->assembler) > 0)
	{
		smlTrace(TRACE_INTERNAL, "%s: Checking if a map command needs to be splitted", __func__);

		/* Do we need fragmentation? */

		gssize space = 0;
		if (!smlAssemblerGetSpace(session->assembler, &space, parent, cmd, error))
			goto error;

		if (!session->frag_command && space <= 0)
		{
			/* Yes, fragmentation is required */
			session->frag_size = 0;

			session->frag_command = cmd;
			smlCommandRef(cmd);

			session->frag_callback = callback;
			session->frag_userdata = userdata;
		}

		/* Fragmentation code */

		if (session->frag_command)
		{
			/* We need to fragment */
			smlTrace(TRACE_INTERNAL, "%s: Space %i. Fragmenting.", __func__, space);

			/* loop until we found the maximum map */
			gsize count = 0;
			do {
				count++;
				smlTrace(TRACE_INTERNAL, "%s: Try %d mappings ...", __func__, count);
				cmd = smlCommandCloneWithNumMappings(session->frag_command, count, error);
				if (!smlAssemblerGetSpace(session->assembler, &space, parent, cmd, error))
					goto error;
				if (space <= 0)
				{
					/* to many mappings */
					count--;
				}
				smlCommandUnref(cmd);
				cmd = NULL;
			} while (space > 0 && smlCommandGetNumMappings(session->frag_command) > count);
			smlTrace(TRACE_INTERNAL, "%s: Add %d mappings ...", __func__, count);

			/* if all mappings are to huge then wait for the next message */

			if (count == 0 &&
			    smlCommandGetPushedBack(session->frag_command))
			{
				g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Even a single mapping is too large for one message.");
				goto error;
			}
			else if (count == 0)
			{
				smlTrace(TRACE_INTERNAL, "%s: Fragmentation is need but starts with the next message.", __func__);
				smlCommandSetPushedBack(session->frag_command, TRUE);
				fragmented = TRUE;
			}
			else if (count < smlCommandGetNumMappings(session->frag_command))
			{
				/* partial command */
				smlTrace(TRACE_INTERNAL, "%s: This is a partial map.", __func__);
				cmd = smlCommandCloneWithNumMappings(session->frag_command, count, error);
				if (!cmd)
					goto error;
				if (!smlCommandRemoveNumMappings(session->frag_command, count, error))
					goto error;
				callback = _smlSessionFragmentStatus;
				userdata = NULL;
				fragmented = TRUE;
			}
			else if (count == smlCommandGetNumMappings(session->frag_command))
			{
				/* complete map command */
				smlTrace(TRACE_INTERNAL, "%s: This is the last chunk", __func__);
				cmd = session->frag_command;
				session->frag_command = NULL;
				callback = session->frag_callback;
				userdata = session->frag_userdata;
				fragmented = FALSE;
			}
			else
			{
				/* bug */
				g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "The counting of mappings failed.");
				goto error;
			}
			smlTrace(TRACE_INTERNAL, "%s: cmd %p", __func__, cmd);
		}
	}
	
	/* We now increment the session ID */
	smlTrace(TRACE_INTERNAL, "%s: cmd %p", __func__, cmd);
	session->lastCommandID++;
	smlCommandSetID(cmd, 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 */
			smlCommandSetPushedBack(cmd, FALSE);
			break;
		case SML_ASSEMBLER_RESULT_MISMATCH:
			if (smlCommandGetPushedBack(cmd)) {
				g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Command is too large");
				goto error;
			}
			smlCommandSetPushedBack(cmd, 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 = smlCommandGetID(cmd);
		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
		smlCommandSetNoResp(cmd, TRUE);
	
	/* We will get a status and a result to a get command
	 * so we register a second callback for the result */
	if (smlCommandGetType(cmd) == SML_COMMAND_TYPE_GET) {
		pending = smlTryMalloc0(sizeof(SmlPendingStatus), error);
		if (!pending) {
			session->lastCommandID--;
			goto error;
		}
		
		pending->callback = callback;
		pending->userdata = userdata;
		pending->cmdID = smlCommandGetID(cmd);
		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__, (*error)->message);
	return SML_ASSEMBLER_RESULT_ERROR;
}

static gboolean
_smlSessionEndCommandInternal (SmlSession *session,
                               SmlCommand *parent,
                               GError **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__, (*error)->message);
	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;
	GError *error = NULL;
	//SmlPendingStatus *pending = NULL;
	smlTrace(TRACE_INTERNAL, "%s: session cmd  %p type %i", __func__, sesscmd->cmd, 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:;
			gsize cmdRef, msgRef = 0;
			if (!smlAssemblerGetNextCmdRef(session->assembler, &cmdRef, &msgRef)) {
				g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "No more statuses were needed");
				goto error;
			}
			
			if (smlStatusGetCommandRef(status) != cmdRef ||
			    smlStatusGetMessageRef(status) != 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,
					smlStatusGetCommandRef(status),
					smlStatusGetMessageRef(status));
				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);
	g_error_free(error);
	return;
}

void
smlSessionIgnoreCommandStatusCallback (SmlSession *session,
                                        SmlStatus *status,
                                        void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, status, userdata);
	SmlCommand *cmd = userdata;
	SmlErrorType errorCode = atoi(smlStatusGetData(status));
	if ((errorCode < 199 || 300 < errorCode) &&
	    errorCode != 508)
	{
		/* This is an error. */
        	if (smlCommandGetAlertType(cmd) == SML_ALERT_NEXT_MESSAGE)
		{
			/* The status of this command must be ignored
			 * because some mobiles like LG KM900 support large objects
			 * but do not understand the next message alert.
			 */
			smlTrace(TRACE_INTERNAL, "%s: Ignoring error status for next message alert.", __func__);
		}
		g_warning("The commmand %d of message %d was replied with status code %d.",
		          smlStatusGetCommandRef(status),
		          smlStatusGetMessageRef(status),
		          errorCode);
	}
	smlCommandUnref(cmd);
		
	smlTrace(TRACE_EXIT, "%s", __func__);
}

gboolean
smlSessionReceiveHeader (SmlSession *session,
                         SmlHeader *header,
                         GError **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 (smlHeaderGetMessageID(header) <= session->lastReceivedMessageID) {
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Message ID not incremented");
		goto error;
	}
	session->lastReceivedMessageID = smlHeaderGetMessageID(header);
	session->lastCommandID++;

	/* check and set the MaxMsgSize */
	if (0 < smlHeaderGetMaxMsgSize(header)) {
		/* 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, smlHeaderGetMaxMsgSize(header));
	}
	if (0 < smlHeaderGetMaxMsgSize(header) &&
	    smlHeaderGetMaxMsgSize(header) < 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, smlHeaderGetMaxMsgSize(header));
	}
	
	/* 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, smlHeaderGetMessageID(header), 1, error))
		goto error;

	/* If RespURI is send in the header
	 * then the URI in the transport layer must be adjusted. */
	if (smlHeaderGetResponseURI(header)) {
		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.
			 */
			g_object_unref(session->target);
		}

		/* build the new target and publish it*/
		session->target = sml_location_new();
		sml_location_set_uri(session->target, smlHeaderGetResponseURI(header));
		sml_location_set_name(session->target, sml_location_get_name(session->orgTarget));
		smlSessionDispatchEvent(session, SML_SESSION_EVENT_RESPONSE_URI, NULL, NULL, NULL, NULL);
		smlTrace(TRACE_INTERNAL, "%s: RespURI: %s, Target: %s", __func__,
			VA_STRING(sml_location_get_uri(session->target)),
			VA_STRING(sml_location_get_uri(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 = sml_location_get_uri(smlHeaderGetSource(header));
		const char *session_target = sml_location_get_uri(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);
			sml_location_set_uri(session->target, header_source);
		}
	}
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return FALSE;
}

gboolean
smlSessionDispatchStatus (SmlSession *session,
                          SmlStatus *status,
                          GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, status, error);
	smlAssert(status);
	CHECK_ERROR_REF
	
	if (smlStatusGetCommandRef(status) == 0) {
		smlAssert(smlStatusGetType(status) == SML_COMMAND_TYPE_HEADER);
		smlAssert(smlStatusGetData(status));
		SmlErrorType errorCode = atoi(smlStatusGetData(status));
		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(smlStatusGetChal(status));
			smlAssert(session->cred);
			if (smlCredGetType(session->cred) == SML_AUTH_TYPE_MD5 &&
			    smlChalGetType(smlStatusGetChal(status)) != SML_AUTH_TYPE_MD5)
			{
				g_set_error(error, SML_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 */
			char *cred_data = smlAuthGetCredString(
						smlChalGetType(smlStatusGetChal(status)),
						smlCredGetUsername(session->cred),
						smlCredGetPassword(session->cred),
						smlChalGetNonce(smlStatusGetChal(status)),
						error);
			smlCredSetData(session->cred, cred_data);
			cred_data = NULL;
			if (!smlCredGetData(session->cred))
				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;
	gboolean 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 == smlStatusGetCommandRef(status) &&
		    pending->msgID == smlStatusGetMessageRef(status)) {
			smlTrace(TRACE_INTERNAL, "%s - Found pending status %s of command %d in message %d",
				__func__, VA_STRING(smlStatusGetData(status)),
				smlStatusGetCommandRef(status), smlStatusGetMessageRef(status));
			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 (smlStatusGetType(status) == SML_COMMAND_TYPE_ALERT && !smlStatusGetAnchor(status)) {
		smlTrace(TRACE_INTERNAL, "%s: Handling status for server alerted sync", __func__);
		SmlErrorType errorCode = atoi(smlStatusGetData(status));
		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.
			 */
			g_set_error(error, SML_ERROR, errorCode, "The SAN alert was rejected.");
			goto error;
		}
	}
	
	g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
		"Received unwanted status %s of command %d in message %d",
		smlStatusGetData(status),
		smlStatusGetCommandRef(status),
		smlStatusGetMessageRef(status));
	goto error;
out:
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return FALSE;
}

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

	gboolean 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;
}

gboolean
smlSessionTryLock (SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, session);
	smlAssert(session);
	smlAssert(session->dispatch_mutex);
	gboolean 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 - %d", __func__, smlQueueLengthPrio(session->command_queue));
		smlQueueDispatchPrio(session->command_queue);
	}
	smlTrace(TRACE_EXIT, "%s - done", __func__);
}

gboolean
smlSessionReceiveBody (SmlSession *session,
                       SmlParser *parser,
                       GError **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;
	gboolean 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) {
		smlTrace(TRACE_INTERNAL, "%s: status iteration.", __func__);
		if (!smlParserGetStatus(parser, &status, error))
			goto error;

		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. */
			smlAssert(session->sessionType == SML_SESSION_TYPE_SERVER);
			smlTrace(TRACE_INTERNAL, "%s - reply to SAN detected", __func__);
			session->lastMessageID = 2;
		}

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

		smlStatusUnref(status);
	}
	
	/* Go through the remaining commands */
	while (1) {
		smlTrace(TRACE_INTERNAL, "%s: command iteration.", __func__);
		result = smlParserGetCommand(parser, &cmd, error);
		if (result == SML_PARSER_RESULT_ERROR)
			goto error;
		if (result == SML_PARSER_RESULT_OTHER) {
			smlTrace(TRACE_INTERNAL, "%s: RESULT_OTHER => next iteration", __func__);
			break;
		}
		
		if (cmd)
			smlCommandSetMessageID(cmd, 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) {
			smlTrace(TRACE_INTERNAL, "%s: missing authentication", __func__);
			session->lastCommandID++;

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

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

		switch (result) {
			case SML_PARSER_RESULT_OPEN:
				smlTrace(TRACE_INTERNAL, "%s: RESULT_OPEN", __func__);
				session->lastCommandID++;
				/* Reserve the place for the status in the assembler */
				if (!smlAssemblerReserveStatus(session->assembler, smlCommandGetID(cmd), session->lastReceivedMessageID, session->lastCommandID, error))
					goto error;
				
				/* Store the parent */
				if (session->parentCommand) {
					g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Already had a parent");
					goto error;
				}
				
				/* 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 (smlCommandGetType(cmd) == SML_COMMAND_TYPE_SYNC) {
					smlTrace(TRACE_INTERNAL, "%s: SYNC => maxObjSize: %i",
						__func__,
						smlCommandGetMaxObjSize(cmd));
					/* 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 (smlCommandGetMaxObjSize(cmd) > 0)
						smlSessionSetRemoteMaxObjSize(session, smlCommandGetMaxObjSize(cmd));
					/* 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;
				smlCommandSetMessageID(cmd, 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;
					
					if (!smlSessionSendReply(session, reply, error))
						goto error;
						
					smlStatusUnref(reply);
				}
				break;
			case SML_PARSER_RESULT_NORMAL:
				smlTrace(TRACE_INTERNAL, "%s: RESULT_NORMAL", __func__);
				session->lastCommandID++;
				/* Reserve the place for the status in the assembler */
				if (!smlAssemblerReserveStatus(session->assembler, smlCommandGetID(cmd), session->lastReceivedMessageID, session->lastCommandID, error))
					goto error;
					
				/* Here we catch Alerts for the next message */
				if (smlCommandGetType(cmd) == SML_COMMAND_TYPE_ALERT) {
					smlTrace(TRACE_INTERNAL, "%s: ALERT with RESULT_NORMAL", __func__);
					if (smlCommandGetAlertType(cmd) == SML_ALERT_NEXT_MESSAGE) {
						smlTrace(TRACE_INTERNAL, "%s: NEXT MESSAGE", __func__);
						SmlStatus *reply = smlCommandNewReply(cmd, SML_NO_ERROR, error);
						if (!reply)
							goto error;
						
						if (!smlSessionSendReply(session, reply, error))
							goto error;
							
						smlStatusUnref(reply);
						
						smlCommandUnref(cmd);
						break;
					} else {
						smlTrace(TRACE_INTERNAL, "%s: Alert maxObjSize: %i",
							__func__, smlCommandGetMaxObjSize(cmd));
						if (smlCommandGetMaxObjSize(cmd) >= 0)
							smlAssemblerSetRemoteMaxObjSize(session->assembler, smlCommandGetMaxObjSize(cmd));
					}
				}
				
				/* Here we catch changes with moreData set */
				if (smlCommandGetType(cmd) == SML_COMMAND_TYPE_ADD || smlCommandGetType(cmd) == SML_COMMAND_TYPE_REPLACE) {

					SmlDataSyncChangeItem *lastItem = smlCommandGetNthChange(cmd, smlCommandGetNumChanges(cmd)-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 (smlCommandGetSize(cmd)) {
							g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
								"Only the first chunk can have the size set");
							goto error;
						}

						/* 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.
						 */
						SmlDataSyncChangeItem *item = smlCommandGetNthChange(cmd, 0);
						if (!item)
							goto error;
						lastItem = smlCommandGetNthChange(session->incomingBuffer, smlCommandGetNumChanges(session->incomingBuffer) -1);
						if (!sml_data_sync_change_item_attach_fragment(lastItem, item, error))
							goto error;
						sml_data_sync_change_item_set_missing_data(lastItem, sml_data_sync_change_item_get_missing_data(item));
						//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 */
						smlCommandTransferItems(cmd, session->incomingBuffer, 1);
						smlCommandFreeFirstChange(cmd);

						/* If the last item of the command is complete
						 * then the command must be prepared for dispatching.
						 */
						/* FIXME: Why do we request lastItem for a second time? */
						//lastItem = smlCommandGetNthChange(session->incomingBuffer, smlCommandGetNumChanges(session->incomingBuffer) -1);
						if (!sml_data_sync_change_item_get_missing_data(lastItem)) {
							smlTrace(TRACE_INTERNAL,
								"%s: Command buffer complete. Dispatching.",
								__func__);

							/* check if the item size is correct */
							if (sml_data_sync_change_item_get_planned_size(lastItem) &&
							    (!sml_data_sync_change_item_get_data(lastItem) ||
							     sml_data_sync_change_item_get_planned_size(lastItem) != strlen(sml_data_sync_change_item_get_data(lastItem))))
							{
								/* We have a size mismatch */
								SmlStatus *reply = smlCommandNewReply(cmd, SML_ERROR_SIZE_MISMATCH, error);
								if (!reply)
									goto error;
								if (!smlSessionSendReply(session, reply, error))
									goto error;
								smlStatusUnref(reply);
								g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
								            "The size of the chunked item is %d. The expected size is %d.",
								            strlen(sml_data_sync_change_item_get_data(lastItem)),
								            sml_data_sync_change_item_get_planned_size(lastItem));
								goto error;
							}
							
							/* 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.
							 */
							smlCommandTransferItems(session->incomingBuffer, cmd, 0);
							smlCommandUnref(session->incomingBuffer);
							session->incomingBuffer = NULL;
						}
					} else if (sml_data_sync_change_item_get_missing_data(lastItem)) {
						/* 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 (!smlCommandGetSize(cmd)) {
							g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
							            "First MoreData item didn't have size set");
							goto error;
						}
						smlAssert(sml_data_sync_change_item_get_planned_size(lastItem));

						session->incomingBuffer = cmd;
						smlCommandRef(cmd);
						//smlTrace(TRACE_INTERNAL, "%s: New Buffer started. Buffered item size: %i. Required: %i",
						//	__func__,
						//	xmlBufferLength(lastItem->buffer),
						//	lastItem->size);
						const char *data = sml_data_sync_change_item_get_data(lastItem);
						char *bin = smlPrintBinary(data, strlen(data));
						smlTrace(TRACE_INTERNAL, "%s: Content so far: %s\n",
							__func__, VA_STRING(bin));
						smlSafeCFree(&bin);
					}
					
					if (sml_data_sync_change_item_get_missing_data(lastItem) == TRUE) {
						smlTrace(TRACE_INTERNAL, "%s: Got item with moreData %i",
							__func__, smlCommandGetMessageID(cmd));
						
						SmlStatus *reply = smlCommandNewReply(cmd, SML_CHUNK_ACCEPTED, error);
						if (!reply)
							goto error;
						
						if (!smlSessionSendReply(session, reply, error))
							goto error;
							
						smlStatusUnref(reply);
						
						smlCommandUnref(cmd);
						break;
					}
				}
				
				/* if the command is a result, we treat it as a status */
				if (smlCommandGetType(cmd) == SML_COMMAND_TYPE_RESULTS) {
					/* Call the callback*/
					if (!smlSessionDispatchStatus(session, smlCommandResultsGetStatus(cmd), error))
						goto error;
				} else {
					/* Dispatch the command */
					smlCommandSetMessageID(cmd, 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;
						
						if (!smlSessionSendReply(session, reply, error))
							goto error;
							
						smlStatusUnref(reply);
					}
				}
				
				smlCommandUnref(cmd);
				break;
			case SML_PARSER_RESULT_CLOSE:
				smlTrace(TRACE_INTERNAL, "%s: RESULT_CLOSE", __func__);
				/* 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 (smlCommandGetType(session->parentCommand) == 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:
				smlTrace(TRACE_INTERNAL, "%s: RESULT_STATUS", __func__);
				goto parse_status;
			default:
				goto error;
		}
	}
	
	/* 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.
	 */
	gboolean final = FALSE;
	gboolean end = FALSE;
	if (!smlParserEnd(parser, &final, &end, error))
		goto error;

	/* 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) {
		gsize 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
		 */
		gsize 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).
		 */
		gsize 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))
				{
					g_set_error(error, SML_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) {
			g_set_error(error, SML_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;

			/* The status of this command must be ignored
			 * because some mobiles like LG KM900 support large objects
			 * but do not understand the next message alert.
			 */
			SmlPendingStatus *pending = smlTryMalloc0(sizeof(SmlPendingStatus), error);
			if (!pending) {
				session->lastCommandID--;
				goto error;
			}
		
			pending->callback = smlSessionIgnoreCommandStatusCallback;
			pending->userdata = alert;
			pending->cmdID = smlCommandGetID(alert);
			pending->msgID = session->lastMessageID;
		
			smlTrace(TRACE_INTERNAL, "%s: Appending pending next message status with cmdID %i and msgID %i", __func__, pending->cmdID, pending->msgID);
			session->pendingReplies = g_list_append(session->pendingReplies, pending);
		}
		/* 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.
				 */
				g_set_error(error, SML_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:
	if (cmd)
		smlCommandUnref(cmd);
	if (status)
		smlStatusUnref(status);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	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__);
}

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

void
smlSessionSetSessionID (SmlSession *session,
                        gsize sessionID)
{
	smlAssert(session);
	session->sessionID = sessionID;
}

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

SmlSessionType
smlSessionGetType (SmlSession *session)
{
	return session->sessionType;
}

void
smlSessionSetType (SmlSession *session,
                   SmlSessionType type)
{
	session->sessionType = type;
}

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

void
smlSessionSetSource (SmlSession *session,
                     SmlLocation *source)
{
	if (!source)
		return;
	if (session->source)
		g_object_unref(session->source);
	session->source = g_object_ref(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 (smlCredGetUsername(cred)) {
		sml_location_set_name(session->source, smlCredGetUsername(cred));
	}
	smlCredRef(session->cred);
}

SmlChal*
smlSessionGetChal (SmlSession *session)
{
	return session->chal;
}

void
smlSessionSetChal (SmlSession *session,
                   SmlChal *chal)
{
	if (session->chal)
		smlChalUnref(session->chal);
	session->chal = chal;
	smlChalRef(chal);
}

gboolean
smlSessionGetEnd (SmlSession *session)
{
	return session->end;
}

void
smlSessionSetEnd (SmlSession *session,
                  gboolean end)
{
	session->end = end;
}

gboolean
smlSessionGetAuthenticate (SmlSession *session)
{
	return session->authenticate;
}

void
smlSessionSetAuthenticate (SmlSession *session,
                           gboolean authenticate)
{
	session->authenticate = authenticate;
}

/*@}*/

/**
 * @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,
               gsize sessionID,
               gsize messageID,
               GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%i, %i, %i, %i, %p, %p, %u, %i, %p)", __func__, sessionType, mimetype, version, protocol, target, source, 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 = 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;
	session->localMaxMsgChanges = 0;
	session->msgChanges = 0;

	if (sessionType == SML_SESSION_TYPE_CLIENT)
		session->sending = TRUE;
	else
		session->sending = FALSE;
	
	session->source = source;
	g_object_ref(source);
		
	session->target = target;
	g_object_ref(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;
	smlAssemblerSetOption(session->assembler, "USE_STRTABLE", "1");
	
	if (!smlAssemblerStart(session->assembler, session, error))
		goto error;
	
	/* 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;
	smlQueueSetHandler(session->command_queue, (SmlQueueHandler)_smlSessionCommandHandler, session);
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, session);
	return session;
error:
	if (session)
		smlSessionUnref(session);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	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)
			g_object_unref(session->target);
		
		if (session->responseURI)
			smlSafeCFree(&(session->responseURI));

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

		if (session->cred)
			smlCredUnref(session->cred);
		
		if (session->chal)
			smlChalUnref(session->chal);
		
		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
 * 
 */
gboolean
smlSessionFlush (SmlSession *session,
                 gboolean final,
                 GError **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__, (*error)->message);
	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
 * 
 */
gboolean
smlSessionEnd (SmlSession *session,
               GError **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", __func__, 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__, (*error)->message);
	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).
		 */
		g_object_unref(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. */
gboolean
smlSessionSetResponseURI (SmlSession *session,
                          const gchar *responseURI,
                          GError **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 (%u) 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,
                          gboolean 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,
                              gboolean 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,
                           gboolean 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,
                          gboolean 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__);
}

void
smlSessionSetEstablished (SmlSession *session,
                          gboolean established)
{
	session->established = established;
}

gboolean
smlSessionGetEstablished (SmlSession *session)
{
	return session->established;
}

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

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

void
smlSessionSetLocalMaxMsgSize (SmlSession *session,
                              gsize 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 - %u", __func__, session->localMaxMsgSize);
}

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

/** 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,
                               gsize 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. */
gsize
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;
}

void
smlSessionSetLocalMaxMsgChanges (SmlSession *session,
                                 gsize size)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %u)", __func__, session, size);
	smlAssert(session);

	session->localMaxMsgChanges = size;
	
	smlTrace(TRACE_EXIT, "%s - %u", __func__, session->localMaxMsgChanges);
}


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

gsize
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
 * 
 */
gboolean
smlSessionSendCommand (SmlSession *session,
                       SmlCommand *cmd,
                       SmlCommand *parent,
                       SmlStatusReplyCb callback,
                       void *userdata,
                       GError **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__, (*error)->message);
	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
 * 
 */
gboolean
smlSessionStartCommand (SmlSession *session,
                        SmlCommand *cmd,
                        SmlCommand *parent,
                        SmlStatusReplyCb callback,
                        void *userdata,
                        GError **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__, (*error)->message);
	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
 * 
 */
gboolean
smlSessionEndCommand (SmlSession *session,
                      SmlCommand *parent,
                      GError **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__, (*error)->message);
	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
 * 
 */
gboolean
smlSessionSendReply (SmlSession *session,
                     SmlStatus *status,
                     GError **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__, (*error)->message);
	return FALSE;
}

gsize
smlSessionGetLastRecvMsgID (SmlSession *session)
{
	return session->lastReceivedMessageID;
}

/*@}*/

/*@}*/
