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

#include "../syncml.h"
#include "../syncml_internals.h"
#include "../sml_error_internals.h"

#include <libsyncml/data_sync_api/defines.h>
#include <libsyncml/data_sync_api/standard.h>
#include <libsyncml/data_sync_api/callbacks.h>

/* required because of function index */
#include <strings.h>

#include "data_sync.h"
#include "data_sync_client.h"
#include "data_sync_server.h"
#include "data_sync_callbacks.h"
#include "data_sync_callbacks.h"
#include "data_sync_devinf.h"
#include "data_sync_loop.h"
#include "transport.h"
#include "libsyncml/objects/sml_ds_server_internals.h"

/* ********************************* */
/* object creation and configuration */
/* ********************************* */

SmlDataSyncObject *smlDataSyncNew(
			SmlSessionType dsType,
			SmlTransportType tspType,
			SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%d, %d, %p)", __func__, dsType, tspType, error);
	CHECK_ERROR_REF

	smlTrace(TRACE_INTERNAL, "%s: libsyncml version: %s", __func__, VERSION);

	SmlDataSyncObject *dsObject = smlTryMalloc0(sizeof(SmlDataSyncObject), error);
	if (!dsObject)
		goto error;
	smlDataSyncObjectRef(dsObject);

	dsObject->dsType = dsType;
	dsObject->tspType = tspType;
	dsObject->version = SML_VERSION_11;
	dsObject->internalState = SML_DATA_SYNC_STATE_NEW;

	dsObject->useNumberOfChanges = TRUE;
	dsObject->useTimestampAnchor = TRUE;
	dsObject->maxObjSize = SML_DEFAULT_MAX_OBJ_SIZE;
	dsObject->maxMsgSize = SML_DEFAULT_MAX_MSG_SIZE;

	dsObject->tsp = smlTransportNew(tspType, error);
	if (!dsObject->tsp)
		goto error;
	switch(tspType)
	{
		case SML_TRANSPORT_OBEX_CLIENT:
			dsObject->funcTspInit    = smlDataSyncTransportObexClientInit;
			dsObject->funcTspConnect = smlDataSyncTransportObexClientConnect;
			break;
		case SML_TRANSPORT_OBEX_SERVER:
			dsObject->funcTspInit    = NULL;
			dsObject->funcTspConnect = NULL;
			break;
		case SML_TRANSPORT_HTTP_SERVER:
			dsObject->funcTspInit    = smlDataSyncTransportHttpServerInit;
			dsObject->funcTspConnect = NULL;
			break;
		case SML_TRANSPORT_HTTP_CLIENT:
			dsObject->funcTspInit    = smlDataSyncTransportHttpClientInit;
			dsObject->funcTspConnect = smlDataSyncTransportHttpClientConnect;
			break;
		default:
			smlErrorSet(error, SML_ERROR_INTERNAL_MISCONFIGURATION,
				"Unknown transport type %d used in __func__.",
				tspType, __func__);
			goto error;
			break;
	}
	switch(dsType)
	{
		case SML_SESSION_TYPE_SERVER:
			dsObject->funcDsInit = smlDataSyncServerInit;
			dsObject->funcDsConnect = NULL;
			break;
		case SML_SESSION_TYPE_CLIENT:
			dsObject->funcDsInit = smlDataSyncClientInit;
			dsObject->funcDsConnect = NULL;
			break;
		default:
			smlErrorSet(error, SML_ERROR_INTERNAL_MISCONFIGURATION,
				"Unknown data sync type %d used in %s.",
				dsType, __func__);
			goto error;
			break;
	}

	smlTrace(TRACE_EXIT, "%s - %p", __func__, dsObject);
	return dsObject;
error:
	if (dsObject) {
		if (dsObject->tsp)
			smlTransportFree(dsObject->tsp);
		smlDataSyncObjectUnref(&dsObject);
	}
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
	return NULL;
}

void smlDataSyncObjectRef(SmlDataSyncObject *dsObject)
{
	g_atomic_int_inc(&(dsObject->refCount));
}

SmlBool smlDataSyncSetOption(
			SmlDataSyncObject *dsObject,
			const char *name,
			const char *value,
			SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %s, %s, %p)", __func__, dsObject, VA_STRING(name), VA_STRING(value), error);
	CHECK_ERROR_REF

	if (!strcmp(SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, name)) {
		if (!strcmp(SML_DATA_SYNC_CONFIG_CONNECTION_SERIAL, value))
			dsObject->conType = SML_TRANSPORT_CONNECTION_TYPE_SERIAL;
		if (!strcmp(SML_DATA_SYNC_CONFIG_CONNECTION_BLUETOOTH, value))
			dsObject->conType = SML_TRANSPORT_CONNECTION_TYPE_BLUETOOTH;
		if (!strcmp(SML_DATA_SYNC_CONFIG_CONNECTION_IRDA, value))
			dsObject->conType = SML_TRANSPORT_CONNECTION_TYPE_IRDA;
		if (!strcmp(SML_DATA_SYNC_CONFIG_CONNECTION_NET, value))
			dsObject->conType = SML_TRANSPORT_CONNECTION_TYPE_NET;
		if (!strcmp(SML_DATA_SYNC_CONFIG_CONNECTION_USB, value))
			dsObject->conType = SML_TRANSPORT_CONNECTION_TYPE_USB;
		if (!smlTransportSetConnectionType(dsObject->tsp, dsObject->conType, error))
			goto error;
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_VERSION, name)) {
		dsObject->version = SML_VERSION_UNKNOWN;
		if (!strcmp("1.0", value))
			dsObject->version = SML_VERSION_10;
		if (!strcmp("1.1", value))
			dsObject->version = SML_VERSION_11;
		if (!strcmp("1.2", value))
			dsObject->version = SML_VERSION_12;
		if (dsObject->version == SML_VERSION_UNKNOWN) {
			smlErrorSet(error, SML_ERROR_INTERNAL_MISCONFIGURATION,
				"Unknown SyncML version %s.", value);
			goto error;
		}
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_AUTH_TYPE, name)) {
		if (!strcmp(value, SML_DATA_SYNC_CONFIG_AUTH_BASIC)) {
			dsObject->authType = SML_AUTH_TYPE_BASIC;
		} else if (!strcmp(value, SML_DATA_SYNC_CONFIG_AUTH_MD5)) {
			dsObject->authType = SML_AUTH_TYPE_MD5;
		} else if (!strcmp(value, SML_DATA_SYNC_CONFIG_AUTH_NONE)) {
			dsObject->authType = SML_AUTH_TYPE_UNKNOWN;
		} else {
			// this is an illegal keyword
			smlErrorSet(error, SML_ERROR_INTERNAL_MISCONFIGURATION,
				 "Unknown authentication type %s.", value);
			goto error;
		}
	} else if (!strcmp(SML_TRANSPORT_CONFIG_URL, name)) {
		dsObject->url = g_strdup(value);
		/* If the Target is not set explicitly
		 * then the URL is used as the Target in the session header.
		 * This is only relevant for HTTP clients today.
		 */
		if (!dsObject->target)
			dsObject->target = g_strdup(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_TARGET, name)) {
		if (dsObject->target)
			smlSafeCFree(&(dsObject->target));
		dsObject->target = g_strdup(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_IDENTIFIER, name)) {
		if (dsObject->identifier)
			smlSafeCFree(&(dsObject->identifier));
		if (value && strlen(value)) {
			dsObject->identifier = g_strdup(value);
		} else {
			smlTrace(TRACE_INTERNAL,
				"%s: set identifier to NULL", __func__);
		}
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_AUTH_USERNAME, name)) {
		dsObject->username = g_strdup(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_AUTH_PASSWORD, name)) {
		dsObject->password = g_strdup(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_USE_WBXML, name)) {
		dsObject->useWbxml = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_USE_STRING_TABLE, name)) {
		dsObject->useStringTable = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_USE_TIMESTAMP_ANCHOR, name)) {
		dsObject->useTimestampAnchor = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_USE_NUMBER_OF_CHANGES, name)) {
		dsObject->useNumberOfChanges = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_USE_LOCALTIME, name)) {
		dsObject->onlyLocaltime = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_ONLY_REPLACE, name)) {
		dsObject->onlyReplace = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_MAX_OBJ_SIZE, name)) {
		dsObject->maxObjSize = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_MAX_MSG_SIZE, name)) {
		dsObject->maxMsgSize = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_FAKE_DEVICE, name)) {
		dsObject->fakeDevice = g_strdup(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_FAKE_MANUFACTURER, name)) {
		dsObject->fakeManufacturer = g_strdup(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_FAKE_MODEL, name)) {
		dsObject->fakeModel = g_strdup(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_FAKE_SOFTWARE_VERSION, name)) {
		dsObject->fakeSoftwareVersion = g_strdup(value);
	} else {
		if (!smlTransportSetConfigOption(dsObject->tsp, name, value, error))
			goto error;
	}

	// the default is syncml:auth-basic
	if ((dsObject->username || dsObject->password) &&
	     dsObject->authType == SML_AUTH_TYPE_UNKNOWN)
	{
		smlTrace(TRACE_INTERNAL,
			"%s: authType is set to default (syncml:auth-basic)", __func__);
		dsObject->authType = SML_AUTH_TYPE_BASIC;
	}
			
	smlTrace(TRACE_EXIT, "%s - TRUE", __func__);
	return TRUE;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
	return FALSE;
}

SmlBool smlDataSyncAddDatastore(SmlDataSyncObject *dsObject,
			const char *contentType,
			const char *target,
			const char *source,
			SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %s, %s, %s, %p)", __func__, dsObject, VA_STRING(contentType), VA_STRING(target), VA_STRING(source), error);
	CHECK_ERROR_REF

	char *lcCT = NULL;
	char *lcSource = NULL;

	SmlDataSyncDatastore *datastore = smlTryMalloc0(sizeof(SmlDataSyncDatastore), error);
	if (!datastore)
		goto error;

	datastore->dsObject = dsObject;
	datastore->syncChanges = NULL;
	datastore->syncContexts = NULL;
	datastore->sourceUri = g_strdup(source);
	datastore->targetUri = g_strdup(target);
	datastore->contentType = g_strdup(contentType);

	dsObject->datastores = g_list_append(dsObject->datastores, datastore);

	/* especially Samsung devices need the datastores during connect */

	if (dsObject->tspType == SML_TRANSPORT_OBEX_CLIENT)
	{
		lcCT = g_ascii_strdown(contentType, strlen(contentType));
		lcSource = g_utf8_strdown(source, strlen(source));
		if (strstr(lcCT, "vcard") &&
		    !smlTransportSetConfigOption(
		        dsObject->tsp,
		        SML_TRANSPORT_CONFIG_DATASTORE,
		        SML_TRANSPORT_CONFIG_DATASTORE_CONTACT,
		        error))
		{
			goto error;
		}
		if (strstr(lcCT, "calendar") &&
		    ( strstr(lcSource, "cal") ||
		      strstr(lcSource, "event")
		    )&&
		    !smlTransportSetConfigOption(
		        dsObject->tsp,
		        SML_TRANSPORT_CONFIG_DATASTORE,
		        SML_TRANSPORT_CONFIG_DATASTORE_EVENT,
		        error))
		{
			goto error;
		}
		if (strstr(lcCT, "calendar") &&
		    strstr(lcSource, "todo") &&
		    !smlTransportSetConfigOption(
		        dsObject->tsp,
		        SML_TRANSPORT_CONFIG_DATASTORE,
		        SML_TRANSPORT_CONFIG_DATASTORE_TODO,
		        error))
		{
			goto error;
		}
		if (strstr(lcCT, "text/plain") &&
		    !smlTransportSetConfigOption(
		        dsObject->tsp,
		        SML_TRANSPORT_CONFIG_DATASTORE,
		        SML_TRANSPORT_CONFIG_DATASTORE_NOTE,
		        error))
		{
			goto error;
		}
		smlSafeCFree(&lcCT);
		smlSafeCFree(&lcSource);
	}

	smlTrace(TRACE_EXIT, "%s - TRUE", __func__);
	return TRUE;
error:
	if (datastore)
		smlSafeFree((gpointer *)&datastore);
	if (lcCT)
		smlSafeCFree(&lcCT);
	if (lcSource)
		smlSafeCFree(&lcSource);
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
	return FALSE;
}

static SmlDataSyncDatastore *smlDataSyncGetDatastoreFromSource(
				SmlDataSyncObject *dsObject,
				const char *source,
				SmlError **error)
{
	CHECK_ERROR_REF
	smlAssert(dsObject);
	smlAssert(source);

	GList *o = dsObject->datastores;
	for (; o; o = o->next) { 
		SmlDataSyncDatastore *datastore = o->data;
		if (!strcmp(datastore->sourceUri, source))
			return datastore;
	}

	smlErrorSet(error, SML_ERROR_INTERNAL_MISCONFIGURATION,
		"Cannot find datastore for source name %s.", source);
	return NULL;
}

/* ***************************** */
/*       register callbacks      */
/* ***************************** */

void smlDataSyncRegisterEventCallback(
			SmlDataSyncObject *dsObject,
			SmlDataSyncEventCallback callback,
			void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	smlAssert(dsObject);
	smlAssert(callback);

	dsObject->eventCallback = callback;
	dsObject->eventUserdata = userdata;

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

void smlDataSyncRegisterGetAlertTypeCallback(
			SmlDataSyncObject *dsObject,
			SmlDataSyncGetAlertTypeCallback callback,
			void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	smlAssert(dsObject);
	smlAssert(callback);

	dsObject->getAlertTypeCallback = callback;
	dsObject->getAlertTypeUserdata = userdata;

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

void smlDataSyncRegisterChangeCallback(
			SmlDataSyncObject *dsObject,
			SmlDataSyncChangeCallback callback,
			void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	smlAssert(dsObject);
	smlAssert(callback);

	dsObject->changeCallback = callback;
	dsObject->changeUserdata = userdata;

	GList *o = dsObject->datastores;
	for (;o;o = o->next) {
		SmlDataSyncDatastore *datastore = o->data;
		if (datastore->session) {
			smlDsSessionGetSync(datastore->session,
				smlDataSyncSyncCallback, datastore);
			smlDsSessionGetChanges(datastore->session,
				smlDataSyncChangeCallback, datastore);
		}
	}

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

void smlDataSyncRegisterChangeStatusCallback(
			SmlDataSyncObject *dsObject,
			SmlDataSyncChangeStatusCallback callback)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	smlAssert(dsObject);
	smlAssert(callback);

	dsObject->changeStatusCallback = callback;
	/* the userdata is set by the AddChange function */

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

void smlDataSyncRegisterMapCallback(
			SmlDataSyncObject *dsObject,
			SmlDataSyncMappingCallback callback,
			void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	smlAssert(dsObject);
	smlAssert(callback);

	dsObject->mappingCallback = callback;
	dsObject->mappingUserdata = userdata;

	GList *o = dsObject->datastores;
	for (;o;o = o->next) {
		SmlDataSyncDatastore *datastore = o->data;
		if (datastore->session) {
			smlDsSessionGetMapping(datastore->session,
				smlDataSyncMappingCallback, datastore);
		}
	}

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


void smlDataSyncRegisterGetAnchorCallback(
			SmlDataSyncObject *dsObject,
			SmlDataSyncGetAnchorCallback callback,
			void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	smlAssert(dsObject);
	smlAssert(callback);

	dsObject->getAnchorCallback = callback;
	dsObject->getAnchorUserdata = userdata;

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

void smlDataSyncRegisterSetAnchorCallback(
			SmlDataSyncObject *dsObject,
			SmlDataSyncSetAnchorCallback callback,
			void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	smlAssert(dsObject);
	smlAssert(callback);

	dsObject->setAnchorCallback = callback;
	dsObject->setAnchorUserdata = userdata;

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

void smlDataSyncRegisterWriteDevInfCallback(
			SmlDataSyncObject *dsObject,
			SmlDataSyncWriteDevInfCallback callback,
			void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	smlAssert(dsObject);
	smlAssert(callback);

	dsObject->writeDevInfCallback = callback;
	dsObject->writeDevInfUserdata = userdata;

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

void smlDataSyncRegisterReadDevInfCallback(
			SmlDataSyncObject *dsObject,
			SmlDataSyncReadDevInfCallback callback,
			void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	smlAssert(dsObject);
	smlAssert(callback);

	dsObject->readDevInfCallback = callback;
	dsObject->readDevInfUserdata = userdata;

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

void smlDataSyncRegisterHandleRemoteDevInfCallback(
			SmlDataSyncObject *dsObject,
			SmlDataSyncHandleRemoteDevInfCallback callback,
			void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	smlAssert(dsObject);
	smlAssert(callback);

	dsObject->handleRemoteDevInfCallback = callback;
	dsObject->handleRemoteDevInfUserdata = userdata;

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

/* ***************************** */
/*          init session         */
/* ***************************** */

SmlBool smlDataSyncInit(SmlDataSyncObject *dsObject, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	CHECK_ERROR_REF
	smlAssert(dsObject);

	if (!dsObject->identifier)
		dsObject->identifier = smlDataSyncDevInfGetIdentifier();

	dsObject->managerMutex = g_mutex_new();

	if (dsObject->funcTspInit &&
	    !dsObject->funcTspInit(dsObject, error))
		goto error;
	if (!dsObject->funcDsInit(dsObject, error))
		goto error;

	dsObject->internalState = SML_DATA_SYNC_STATE_INITIALIZED;

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

SmlBool smlDataSyncRun(SmlDataSyncObject *dsObject, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	CHECK_ERROR_REF

	/* If a server was initialized then you cannot do more
	 * than waiting for the remote client.
	 */

	if (dsObject->funcTspConnect &&
	    !dsObject->funcTspConnect(dsObject, error))
                goto error;
	if (dsObject->funcDsConnect &&
	    !dsObject->funcDsConnect(dsObject, error))
                goto error;

	if (!smlDataSyncLoopStart(dsObject, error))
		goto error;

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

/* synchronize */

SmlBool smlDataSyncAddChange(
			SmlDataSyncObject *dsObject,
			const char *source,
			SmlChangeType type,
			const char *name,
			const char *data,
			unsigned int size,
			void *userdata, /* for SmlDataSyncChangeStatusCallback */
			SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%s)", __func__, VA_STRING(source));

	/* check params */
	CHECK_ERROR_REF
	smlAssert(dsObject);
	smlAssert(source);
	smlAssert(type);

	/* create a new change */
	SmlDataSyncChange *change = smlTryMalloc0(sizeof(SmlDataSyncChange), error);
	if (!change)
		goto error;

	/* fill the new change */
	change->type = type;
	change->name = g_strdup(name);
	change->userdata = userdata;

	/* determine the datastore */
	change->datastore = smlDataSyncGetDatastoreFromSource(dsObject, source, error);
	if (!change->datastore)
		goto error;
	SmlDataSyncDatastore *datastore = change->datastore;

	/* copy data */
	if (data) {
		smlAssert(datastore->contentType);
		smlAssert(index(datastore->contentType, '/'));
		size_t appClassLength = ((size_t) index(datastore->contentType, '/')) -
					((size_t) datastore->contentType);
		if ( ( strstr(datastore->contentType, SML_CONTENT_TYPE_APPLICATION) == datastore->contentType &&
		       appClassLength == strlen(SML_CONTENT_TYPE_APPLICATION) ) ||
		     ( strstr(datastore->contentType, SML_CONTENT_TYPE_AUDIO) == datastore->contentType &&
		       appClassLength == strlen(SML_CONTENT_TYPE_AUDIO) ) ||
		     ( strstr(datastore->contentType, SML_CONTENT_TYPE_IMAGE) == datastore->contentType &&
		       appClassLength == strlen(SML_CONTENT_TYPE_IMAGE) ) ||
		     ( strstr(datastore->contentType, SML_CONTENT_TYPE_MESSAGE) == datastore->contentType &&
		       appClassLength == strlen(SML_CONTENT_TYPE_MESSAGE) ) ||
		     ( strstr(datastore->contentType, SML_CONTENT_TYPE_VIDEO) == datastore->contentType &&
		       appClassLength == strlen(SML_CONTENT_TYPE_AUDIO) ) )
		{
			/* binary data must be base64 encoded */
			change->data = g_base64_encode((const unsigned char *) data, size);
			if (!change->data) {
				smlErrorSet(error, SML_ERROR_GENERIC,
					    "The base 64 encoding of glib failed.");
				goto error;
			}
			change->size = strlen(change->data);
		} else {
			change->data = smlTryMalloc0(size+1, error);
			if (!change->data)
				goto error;
			memcpy(change->data, data, size);
			change->size = size;
		}
	} else {
		change->data = NULL;
		change->size = 0;
	}

	/* append change to datastore */
	datastore->changes = g_list_append(datastore->changes, change);

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

SmlBool smlDataSyncSendChanges(SmlDataSyncObject *dsObject, SmlError **error)
{
	/* This means that all alerts were received
	 * and all changes were added.
	 * So let us flush the session. */
	smlTrace(TRACE_ENTRY, "%s", __func__);
	CHECK_ERROR_REF

	/* Verify that the library is in the correct state */

	if (dsObject->internalState >= SML_DATA_SYNC_STATE_SENT_CHANGES)
	{
		smlErrorSet(error, SML_ERROR_GENERIC,
			"The function %s is called to late or more than once.", __func__);
		goto error;
	} else {
		dsObject->internalState = SML_DATA_SYNC_STATE_SENT_CHANGES;
	}
	if (dsObject->dsType == SML_SESSION_TYPE_SERVER &&
	    dsObject->actualPackage != SML_PACKAGE_4)
	{
		smlErrorSet(error, SML_ERROR_GENERIC,
			"This is not the server's sync package (%d).",
			dsObject->actualPackage);
		goto error;
	}
	if (dsObject->dsType == SML_SESSION_TYPE_CLIENT &&
	    dsObject->actualPackage != SML_PACKAGE_3)
	{
		smlErrorSet(error, SML_ERROR_GENERIC,
			"This is not the client's sync package (%d).",
			dsObject->actualPackage);
		goto error;
	}

	/* iterate over all datastores */

	GList *o = dsObject->datastores;
	for (; o; o = o->next) { 
		SmlDataSyncDatastore *datastore = o->data;
		smlAssert(datastore);

		int num = g_list_length(datastore->changes);
		if (!smlDsSessionSendSync(
				datastore->session,
				num,
				smlDataSyncSyncStatusCallback,
				datastore,
				error))
			goto error;
		
		int i = 0;
		for (i = 0; i < num; i++) {
			SmlDataSyncChange *change = g_list_nth_data(datastore->changes, i);
			if (!smlDsSessionQueueChange(
					datastore->session,
    					change->type,
					change->name,
					change->data,
					change->size,
					datastore->contentType,
					smlDataSyncChangeStatusCallback,
					change,
					error))
				goto error;
		}

		if (!smlDsSessionCloseSync(datastore->session, error))
			goto error;
	}

	/* Send the next package */

	if (!smlSessionFlush(dsObject->session, TRUE, error))
		goto error;
	
	smlTrace(TRACE_EXIT, "%s - TRUE", __func__);
	return TRUE;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
	return FALSE;
}

SmlBool smlDataSyncAddMapping(
			SmlDataSyncObject *dsObject,
			const char *source,
			const char *remoteID,
			const char *localID,
			SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%s)", __func__, VA_STRING(source));

	/* check params */
	CHECK_ERROR_REF
	smlAssert(dsObject);
	smlAssert(source);
	smlAssert(remoteID);
	smlAssert(localID);

	/* A map can only be created if a sync from a server was
	 * received. So it makes no sense to cache the mapping.
	 * Therefore the map will be set immediately.
	 */

	SmlDataSyncDatastore *datastore = NULL;
	datastore = smlDataSyncGetDatastoreFromSource(dsObject, source, error);
	if (!datastore)
		goto error;
	if (!smlDsSessionQueueMap(datastore->session, remoteID, localID, error))
		goto error;

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

SmlBool smlDataSyncSendMap(SmlDataSyncObject *dsObject, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	CHECK_ERROR_REF
	smlAssert(dsObject);

	/* Verify that the library is in the correct state */

	if (dsObject->dsType == SML_SESSION_TYPE_SERVER)
	{
		smlErrorSet(error, SML_ERROR_GENERIC, "An OMA DS server never sends a map.");
		goto error;
	}
	if (dsObject->dsType == SML_SESSION_TYPE_CLIENT &&
	    dsObject->actualPackage != SML_PACKAGE_5)
	{
		smlErrorSet(error, SML_ERROR_GENERIC, "Missing server's sync package.");
		goto error;
	}

	/* iterate over all datastores */

	GList *o = dsObject->datastores;
	for (; o; o = o->next) { 
		SmlDataSyncDatastore *datastore = o->data;
		smlAssert(datastore);

		/* FIXME: today we ignore the answer */
		if (!smlDsSessionCloseMap(datastore->session, smlDataSyncMapStatusCallback, datastore, error))
			goto error;
	}

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


const SmlLocation *smlDataSyncGetTarget(
			SmlDataSyncObject *dsObject, 
			SmlError **error)
{
	smlTrace(TRACE_INTERNAL, "%s(%p, %p)", __func__, dsObject, error);
	smlAssert(dsObject);
	return smlSessionGetTarget(dsObject->session);
}


/* close session */

void smlDataSyncObjectUnref(SmlDataSyncObject **dsObject)
{
	if (g_atomic_int_dec_and_test(&((*dsObject)->refCount))) {

		/* stop the dispatcher loop */
		if ((*dsObject)->thread)
			smlDataSyncLoopStop((*dsObject));

		/* stop and free manager */
		if ((*dsObject)->manager) {
			smlManagerStop((*dsObject)->manager);
			smlManagerFree((*dsObject)->manager);
		}
		if ((*dsObject)->managerMutex)
			g_mutex_free((*dsObject)->managerMutex);

		/* cleanup transport */
		if ((*dsObject)->tsp) {
			SmlError *error = NULL;
			if ((*dsObject)->dsType == SML_SESSION_TYPE_CLIENT &&
			    SML_DATA_SYNC_STATE_CONNECTED >= (*dsObject)->internalState &&
			    (*dsObject)->internalState <= SML_DATA_SYNC_STATE_DISCONNECTED &&
			    !smlTransportDisconnect((*dsObject)->tsp, NULL, &error)) {
				/* just ignore the error */
				smlTrace(TRACE_ERROR, "%s: %s",
					__func__, smlErrorPrint(&error));
				smlErrorDeref(&error);
			}
			/* FIXME: we have to wait here for the disconnect ... */
			if ((*dsObject)->internalState >= SML_DATA_SYNC_STATE_INITIALIZED &&
			    !smlTransportFinalize((*dsObject)->tsp, &error)) {
				smlTrace(TRACE_ERROR, "%s: %s",
					__func__, smlErrorPrint(&error));
				smlErrorDeref(&error);
			}
			smlTransportFree((*dsObject)->tsp);
		}

		/* cleanup configuration */
		if ((*dsObject)->url)
			smlSafeCFree(&((*dsObject)->url));
		if ((*dsObject)->target)
			smlSafeCFree(&((*dsObject)->target));
		if ((*dsObject)->identifier)
			smlSafeCFree(&((*dsObject)->identifier));
		if ((*dsObject)->username)
			smlSafeCFree(&((*dsObject)->username));
		if ((*dsObject)->password)
			smlSafeCFree(&((*dsObject)->password));
		if ((*dsObject)->fakeDevice)
			smlSafeCFree(&((*dsObject)->fakeDevice));
		if ((*dsObject)->fakeManufacturer)
			smlSafeCFree(&((*dsObject)->fakeManufacturer));
		if ((*dsObject)->fakeModel)
			smlSafeCFree(&((*dsObject)->fakeModel));
		if ((*dsObject)->fakeSoftwareVersion)
			smlSafeCFree(&((*dsObject)->fakeSoftwareVersion));

		/* cleanup datastores */
		while((*dsObject)->datastores) {
			SmlDataSyncDatastore *datastore = (*dsObject)->datastores->data;
			(*dsObject)->datastores = g_list_remove(
							(*dsObject)->datastores,
							datastore);
			if (datastore->sourceUri)
				smlSafeCFree(&(datastore->sourceUri));
			if (datastore->targetUri)
				smlSafeCFree(&(datastore->targetUri));
			if (datastore->contentType)
				smlSafeCFree(&(datastore->contentType));
			if (datastore->remoteNext)
				smlSafeCFree(&(datastore->remoteNext));
			if (datastore->localNext)
				smlSafeCFree(&(datastore->localNext));
			if (datastore->session)
				smlDsSessionUnref(datastore->session);
			if (datastore->server)
				smlDsServerFree(datastore->server);

			while(datastore->changes) {
				SmlDataSyncChange *change = datastore->changes->data;
				datastore->changes = g_list_remove(
							datastore->changes,
							change);
				if (change->name)
					smlSafeCFree(&(change->name));
				if (change->data)
					smlSafeCFree(&(change->data));
				smlSafeFree((gpointer *)&change);
			}

			smlSafeFree((gpointer *)&datastore);
		}

		/* cleanup authentication */
		if ((*dsObject)->auth)
			smlAuthFree((*dsObject)->auth);

		/* cleanup session */
		if ((*dsObject)->session)
			smlSessionUnref((*dsObject)->session);

		/* cleanup device information */
		if ((*dsObject)->localDevInf)
			smlDevInfUnref((*dsObject)->localDevInf);
		if ((*dsObject)->remoteDevInf)
			smlDevInfUnref((*dsObject)->remoteDevInf);
		if ((*dsObject)->agent)
			smlDevInfAgentFree((*dsObject)->agent);

		smlSafeFree((gpointer *) dsObject);
	}
}

/* internal functions */

void smlDataSyncSendEvent(
			SmlDataSyncObject *dsObject,
			SmlDataSyncEventType type,
			void *userdata,
			SmlError *error)
{
	/* this functionworks synchronous today */
	smlAssert(dsObject);
	smlAssert(dsObject->eventCallback);
	dsObject->eventCallback(dsObject, type, userdata, error);
}

