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

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

#include "sml_transport.h"
#include "sml_md5.h"

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

SmlNotification*
smlNotificationNew (SmlNotificationVersion version,
                    SmlNotificationUIMode mode,
                    SmlNotificationInitiator init,
                    guint sessionID,
                    const gchar *identifier,
                    const gchar *target,
                    SmlMimeType type,
                    GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%i, %i, %i, %i, %s, %s, %i, %p)", __func__, version, mode, init, sessionID, VA_STRING(identifier), VA_STRING(target), type, error);
	CHECK_ERROR_REF

	SmlNotification *san = smlTryMalloc0(sizeof(SmlNotification), error);
	if (!san)
		goto error;
	
	san->version = version;
	san->mode = mode;
	san->init = init;
	san->sessionID = sessionID;
	san->identifier = g_strdup(identifier);
	san->type = type;
	san->cred = NULL;
	san->sessionType = SML_SESSION_TYPE_SERVER;
	san->manager = NULL;
	
	san->target = sml_location_new();
	sml_location_set_uri(san->target, target);
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, san);
	return san;
error:
	if (san) {
		if (san->identifier)
			g_free(san->identifier);
		smlSafeFree((gpointer *)&san);
	}
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

void
smlNotificationSetCred (SmlNotification *san,
                        SmlCred *cred)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	smlAssert(san);
	smlAssert(cred);
	if (san->cred)
		smlCredUnref(san->cred);
	san->cred = cred;
	// we only set credentials if this is a client
	// if we set no credentials then we always assume a server
	// FIXME: this is a hack because the original implementation
	// FIXME: only support server
	// Only one question which client sends a notification!?
	san->sessionType = SML_SESSION_TYPE_CLIENT;
	smlCredRef(cred);
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void
smlNotificationSetManager (SmlNotification *san,
                           SmlManager *manager)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	smlAssert(san);
	smlAssert(manager);
	san->manager = manager;
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void
smlNotificationSetMimeType (SmlNotification *san,
                            SmlMimeType mimeType)
{
	san->type = mimeType;
}

void
smlNotificationSetSessionType (SmlNotification *san,
                               SmlSessionType sessionType)
{
	san->sessionType = sessionType;
}

void
smlNotificationSetTarget (SmlNotification *san,
                          SmlLocation *target)
{
	san->target = target;
	g_object_ref(san->target);
}

void
smlNotificationFree (SmlNotification *san)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, san);
	smlAssert(san);
	
	while (san->alerts) {
		SmlSanAlert *alert = san->alerts->data;
		san->alerts = g_list_remove(san->alerts, alert);
		smlNotificationFreeAlert(alert);
	}
	
	if (san->target) {
		g_object_unref(san->target);
		san->target = NULL;
	}
	
	if (san->cred) {
		smlCredUnref(san->cred);
		san->cred = NULL;
	}

	if (san->identifier)
		smlSafeCFree(&(san->identifier));
	smlSafeFree((gpointer *)&san);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

gboolean
smlNotificationNewAlert (SmlNotification *san,
                         SmlAlertType type,
                         const gchar *contenttype,
                         const gchar *serverURI,
                         GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, error);
	CHECK_ERROR_REF
	smlAssert(type >= 206 && type <= 210);

	SmlSanAlert *alert = smlTryMalloc0(sizeof(SmlSanAlert), error);
	if (!alert)
		goto error;

	alert->type = type;
	alert->contenttype = g_strdup(contenttype);
	alert->serverURI = g_strdup(serverURI);

	san->alerts = g_list_append(san->alerts, alert);

	smlTrace(TRACE_EXIT, "%s: %p", __func__, alert);
	return TRUE;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return FALSE;
}

void
smlNotificationFreeAlert (SmlSanAlert *alert)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, alert);
	smlAssert(alert);
	
	smlSafeCFree(&(alert->contenttype));
	smlSafeCFree(&(alert->serverURI));
	smlSafeFree((gpointer *)&alert);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

SmlNotification*
smlNotificationParse (const gchar *data,
                      gsize size,
                      GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p)", __func__, data, size, error);
	CHECK_ERROR_REF
	smlAssert(data);
	smlAssert(size);

	SmlNotification *san = NULL;
	
	if (size < 25) {
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Size too small");
		goto error;
	}
	
	guint8 idLength = (guint8) data[23];
	if (size < (25 + idLength)) {
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Size too small2");
		goto error;
	}
	
	san = smlTryMalloc0(sizeof(SmlNotification), error);
	if (!san)
		goto error;
	san->type = SML_MIMETYPE_SAN;

	/* Version */
	guint8 version = ((guint8)data[16]) << 2;
	version = version | ((guint8)data[17]) >> 6;
	switch (version) {
		case 12:
			san->version = SML_SAN_VERSION_12;
			break;
		default:
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Unknown version");
			goto error;
	}
	
	/* ui mode */
	san->mode = (((guint8)data[17]) & 0x30) >> 4;
	
	/* initiator */
	san->init = (((guint8)data[17]) & 0x08) >> 3;

	/* session id */
	san->sessionID = ((guint8)data[21]) << 8;
	san->sessionID = san->sessionID | (guint8)data[22];
	
	/* server identifier */
	if (idLength) {
		/* the trailing NULL byte is usually not present in the SAN */
		san->identifier = smlTryMalloc0(idLength + 1, error);
		if (!san->identifier)
			goto error;
	
		memcpy(san->identifier, data + 24, idLength);
	}
	
	guint8 numSync = ((guint8)data[24 + idLength]) >> 4;
	data += 25 + idLength;
	guint8 alertLength = 25 + idLength;

	unsigned int i = 0;
	for (i = 0; i < numSync; i++) {
		if (size < alertLength + 5) {
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Size too small3");
			goto error;
		}
		
		idLength = (guint8)data[4];
		if (size < (alertLength + 5 + idLength)) {
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Size too small4");
			goto error;
		}
		alertLength = alertLength + 5 + idLength;
		
		/* Alert type + future */
		SmlAlertType alert_type = (((guint8)data[0]) >> 4) + 200;
		if (alert_type < 206 || alert_type > 210) {
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Wrong alert type");
			goto error;
		}
		
		guint32 contenttype = ((guint8)data[1]) << 16;
		contenttype = contenttype | ((guint8)data[2]) << 8;
		contenttype = contenttype | ((guint8)data[3]);
		
		/* The contenttype */
		const char *alert_ct;
		switch (contenttype) {
			case 0x00:
				/* This is exactly like defined in "DS Protocol" section
				 * 12.4.13 "Content Type" of the official OMA document
				 * OMA-TS-DS_Protocol-V1_2_1-20070810-A.pdf.
				 */ 
				alert_ct = NULL;
				break;
			case 0x07:
				alert_ct = SML_ELEMENT_TEXT_VCARD;
				break;
			case 0x06:
				alert_ct = SML_ELEMENT_TEXT_VCAL;
				break;
			case 0x03:
				alert_ct = SML_ELEMENT_TEXT_PLAIN;
				break;
			case 0x0305:
				alert_ct = SML_ELEMENT_TEXT_ICAL;
				break;
			case 0x0306:
				alert_ct = SML_ELEMENT_APPLICATION_OMA_DS_EMAIL;
				break;
			case 0x0307:
				alert_ct = SML_ELEMENT_APPLICATION_OMA_DS_FILE;
				break;
			case 0x0308:
				alert_ct = SML_ELEMENT_APPLICATION_OMA_DS_FOLDER;
				break;
			case 0x0309:
				alert_ct = SML_ELEMENT_TEXT_VCARD_30;
				break;
			default:
				/* http://www.openmobilealliance.org/Tech/omna/omna-wsp-content-type.aspx */
				g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "The OMNA WSP Content Type Number 0x%xd is not supported.", contenttype);
				goto error;
		}
		
		/* The server uri */
		char *alert_uri;
		if (idLength) {
			/* the trailing NULL byte is usually not present in the SAN */
			alert_uri = smlTryMalloc0(idLength + 1, error);
			if (!alert_uri)
				goto error;

			memcpy(alert_uri, data + 5, idLength);
		}
		data += 5 + idLength;

		/* Create new alert */
		if (!smlNotificationNewAlert(san, alert_type, alert_ct, alert_uri, error))
			goto error;

		/* Cleanup */
		if (idLength)
			smlSafeCFree(&alert_uri);
	}

	if (size > alertLength) {
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Extra chunk at the end of the buffer");
		goto error;
	}

	smlTrace(TRACE_EXIT, "%s: %p", __func__, san);
	return san;
error:
	if (san) {
		while (san->alerts) {
			smlNotificationFreeAlert(san->alerts->data);
			san->alerts = g_list_delete_link(san->alerts, san->alerts);
		}
		if (san->identifier)
			smlSafeCFree(&(san->identifier));
		smlSafeFree((gpointer *)&san);
	}
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

gboolean
smlNotificationAssemble11 (SmlNotification *san,
                           gchar **data,
                           gsize *size,
                           SmlProtocolVersion version,
                           GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %i, %p)", __func__, san, data, size, version, error);
	CHECK_ERROR_REF
	smlAssert(san);
	smlAssert(data);
	smlAssert(size);

	/* If a notification is sent via a normal SyncML message
	 * then the used session must be managed to guarantee the
	 * consistency of the session.
	 */
	smlAssert(san->manager);

	SmlAssembler *assm = NULL;
	SmlLocation *source = NULL;
	SmlSession *session = NULL;
	SmlSanAlert *alert = NULL;
	SmlCommand *cmd = NULL;

	source = sml_location_new();
	sml_location_set_uri(source, san->identifier);

	session = smlSessionNew(san->sessionType, san->type, version, SML_PROTOCOL_SYNCML, san->target, source, smlManagerGetNewSessionID(san->manager), 0, error);
	if (!session) {
		g_object_unref(source);
		goto error;
	}
	smlManagerSessionAdd(san->manager, session, NULL, error);

	assm = smlAssemblerNew(san->type, 0, error);
	if (!assm)
		goto error;
	
	if (!smlAssemblerStart(assm, session, error))
		goto error;
	
	if (!smlAssemblerAddHeader(assm, session, error))
		goto error;
	
	GList *a = NULL;
	int i = 1;
	for (a = san->alerts; a; a = a->next) {
		alert = a->data;
		
		
		SmlLocation *loc = sml_location_new();
		sml_location_set_uri(loc, alert->serverURI);
		
		cmd = smlCommandNewAlert(SML_ALERT_TWO_WAY_BY_SERVER, NULL, loc, NULL, NULL, alert->contenttype, error);
		if (!cmd) {
			g_object_unref(loc);
			goto error;
		}
		
		smlCommandSetID(cmd, i);
		i++;
		
		if (!smlAssemblerStartCommand(assm, NULL, cmd, error) == SML_ASSEMBLER_RESULT_OK)
			goto error;
			
		if (!smlAssemblerEndCommand(assm, NULL, error))
			goto error;
			
		g_object_unref(loc);
		
		smlCommandUnref(cmd);
	}
	
	if (!smlAssemblerRun(assm, data, size, NULL, TRUE, error))
		goto error;
	
	smlAssemblerFlush(assm);
	
	smlSessionUnref(session);
	
	g_object_unref(source);
	
	smlAssemblerFree(assm);
	
	char *hex = smlPrintHex(*data, *size);
	smlTrace(TRACE_INTERNAL, "San packet assembled: %s", hex);
	smlSafeCFree(&hex);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
error:
	if (cmd)
		smlCommandUnref(cmd);
	if (session)
		smlSessionUnref(session);
	if (assm)
		smlAssemblerFree(assm);
	*data = NULL;
	*size = 0;
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return FALSE;
}

static gboolean
_smlNotificationAssemble12 (SmlNotification *san,
                            gchar **data,
                            gsize *size,
                            GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, san, data, size, error);
	CHECK_ERROR_REF
	smlAssert(san);
	smlAssert(data);
	smlAssert(*data == NULL);
	smlAssert(size);
	unsigned int numsync = 0;
	SmlSanAlert *alert = NULL;
	
	/* Calculate the length of the san data */
	unsigned int length = 16 /* Digest */ + 8 /* Hdr */ + strlen(san->identifier) /* Identifier */ + 1 /* num-sync + future */;
	GList *a = NULL;
	for (a = san->alerts; a; a = a->next) {
		alert = a->data;
		length += 5 + strlen(alert->serverURI);
		numsync++;
	}
	
	/* Malloc the necessary space */
	char *buffer = smlTryMalloc0(length, error);
	if (!buffer)
		goto error;
	*data = buffer;
	*size = length;

	/* the digest must be created at the end */
	
	/* Create Header */
	/* Version - 10 bit */
	buffer[16] = 0x03;

	/* ui mode */
	buffer[17] = buffer[17] | (san->mode << 4);
	
	/* initiator */
	buffer[17] = buffer[17] | (san->init << 3);
	
	/* future use is 0 for now - 27 bit */

	/* session id */
	buffer[21] = san->sessionID >> 8;
	buffer[22] = san->sessionID;
	
	/* server identifier without trailing NULL byte */
	buffer[23] = strlen(san->identifier);
	strncpy(buffer + 24, san->identifier, strlen(san->identifier));
	
	buffer += 24 + strlen(san->identifier);
	
	/* Num syncs + future */
	buffer[0] = numsync << 4;
	buffer++;
	
	for (a = san->alerts; a; a = a->next) {
		alert = a->data;
		/* Alert type + future */
		buffer[0] = (alert->type - 200) << 4;
		
		/* The contenttype */
		if (!strcmp(alert->contenttype, SML_ELEMENT_TEXT_VCARD)) {
			buffer[3] = 0x07;
		} else if (!strcmp(alert->contenttype, SML_ELEMENT_TEXT_VCAL)) {
			buffer[3] = 0x06;
		} else if (!strcmp(alert->contenttype, SML_ELEMENT_TEXT_PLAIN)) {
			buffer[3] = 0x03;
		} else if (!strcmp(alert->contenttype, SML_ELEMENT_TEXT_ICAL)) {
			buffer[2] = 0x03;
			buffer[3] = 0x05;
		} else if (!strcmp(alert->contenttype, SML_ELEMENT_APPLICATION_OMA_DS_EMAIL)) {
			buffer[2] = 0x03;
			buffer[3] = 0x06;
		} else if (!strcmp(alert->contenttype, SML_ELEMENT_APPLICATION_OMA_DS_FILE)) {
			buffer[2] = 0x03;
			buffer[3] = 0x07;
		} else if (!strcmp(alert->contenttype, SML_ELEMENT_APPLICATION_OMA_DS_FOLDER)) {
			buffer[2] = 0x03;
			buffer[3] = 0x08;
		} else if (!strcmp(alert->contenttype, SML_ELEMENT_TEXT_VCARD_30)) {
			buffer[2] = 0x03;
			buffer[3] = 0x09;
		} else {
			/* http://www.openmobilealliance.org/Tech/omna/omna-wsp-content-type.aspx */
			smlTrace(TRACE_INTERNAL,
				"%s: The OMNA WSP Content Type Number is unknown for %s.",
				__func__, alert->contenttype);
			/* All bytes are already set to zero. */
		}
		
		/* The server uri without a trailing NULL byte */
		buffer[4] = strlen(alert->serverURI);
		strncpy(buffer + 5, alert->serverURI, strlen(alert->serverURI));
		buffer += 5 + strlen(alert->serverURI);
	}

	/* Create the digest */

	/* DO NOT USE buffer because this pointer was manipulated
	 * ALWAYS USE *data.
	 */

	/* base64 encoded MD5 digest of the notification */
	unsigned char digest[16];
	smlMD5GetDigest (*data+16, length - 16 /* digest */, digest);
	char *b64_san = g_base64_encode(digest, 16);
	smlTrace(TRACE_INTERNAL, "%s: b64(md5(san)) ::= %s", __func__, b64_san);

	/* base64 encoded MD5 digest of username:password */
	const char *username = "";
	const char *password = "";
	if (san->cred) {
		username = smlCredGetUsername(san->cred);
		password = smlCredGetPassword(san->cred);
	}
	char *auth = g_strdup_printf("%s:%s", username, password);
	smlMD5GetDigest (auth, strlen(auth), digest);
	smlSafeCFree(&auth);
	char *b64_auth = g_base64_encode(digest, 16);
	smlTrace(TRACE_INTERNAL, "%s: b64(md5(username:password)) ::= %s", __func__, b64_auth);

	/* MD5 digest of auth:nonce:notification (empty nonce) */
	char *complete = g_strdup_printf("%s::%s", b64_auth, b64_san);
	smlSafeCFree(&b64_auth);
	smlSafeCFree(&b64_san);
	smlMD5GetDigest (complete, strlen(complete), digest);
	smlSafeCFree(&complete);
	smlTrace(TRACE_INTERNAL, "%s: md5(auth::san) complete", __func__);

	/* copy digest to buffer */
	memcpy(*data, digest, 16);
	smlTrace(TRACE_INTERNAL, "%s: md5(auth::san) copied to san", __func__);

	/* debug */

	char *hex = smlPrintHex(*data, *size);
	smlTrace(TRACE_INTERNAL, "San packet assembled: %s", hex);
	smlSafeCFree(&hex);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
error:
	if (*data)
		smlSafeCFree(data);
	*size = 0;
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return FALSE;
}

gboolean
smlNotificationAssemble (SmlNotification *san,
                         gchar **data,
                         gsize *size,
                         GError **error)
{
	CHECK_ERROR_REF
	switch (san->version) {
		case SML_SAN_VERSION_12:
			return _smlNotificationAssemble12(san, data, size, error);
		case SML_SAN_VERSION_10:
			return smlNotificationAssemble11(san, data, size, SML_VERSION_10, error);
		case SML_SAN_VERSION_11:
			return smlNotificationAssemble11(san, data, size, SML_VERSION_11, error);
		case SML_SAN_VERSION_UNKNOWN:
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Unknown SAN version.");
			break;
	}
	
	return FALSE;
}

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

	SmlTransportData *tspdata = NULL;
	char *data = NULL;
	gsize size = 0;

	if (!smlNotificationAssemble(san, &data, &size, error))
		goto error;

	if (san->version == SML_SAN_VERSION_12)
		tspdata = smlTransportDataNew(data, size, SML_MIMETYPE_SAN, TRUE, error);
	else
		tspdata = smlTransportDataNew(data, size, san->type, TRUE, error);
	
	if (!tspdata)
		goto error;
	/* Do not free data because smlTransportDateNew does copy by reference. */
	data = NULL;
	
	/* For SyncML 1.2 and OBEX Transport we use SML_MIMETYPE_SAN for PUT and use the regular
	   SyncML message type (xml/wbxml) for GET. To avoid that we response after the PUT with
	   a GET SML_MIMETYPE_SAN request. */
	if (san->version == SML_SAN_VERSION_12)
		smlTransportDataSetRequestedType(tspdata, san->type);

	if (!smlTransportSend(tsp, NULL, tspdata, error))
		goto error;

	smlTransportDataDeref(tspdata);

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

error:
	if (tspdata)
		smlTransportDataDeref(tspdata);
	if (data)
		smlSafeCFree(&data);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return FALSE;
}

SmlNotificationVersion
smlNotificationGetVersion (SmlNotification *san)
{
	smlAssert(san);
	return san->version;
}

SmlNotificationUIMode
smlNotificationGetMode (SmlNotification *san)
{
	smlAssert(san);
	return san->mode;
}

SmlNotificationInitiator
smlNotificationGetInitiator (SmlNotification *san)
{
	smlAssert(san);
	return san->init;
}

guint
smlNotificationGetSessionID (SmlNotification *san)
{
	smlAssert(san);
	return san->sessionID;
}

const gchar*
smlNotificationGetIdentifier (SmlNotification *san)
{
	smlAssert(san);
	return san->identifier;
}

gsize
smlNotificationNumAlerts (SmlNotification *san)
{
	smlAssert(san);
	return g_list_length(san->alerts);
}

SmlSanAlert*
smlNotificationNthAlert (SmlNotification *san,
                         gsize nth)
{
	smlAssert(san);
	return g_list_nth_data(san->alerts, nth);
}

SmlAlertType
smlSanAlertGetType (SmlSanAlert *alert)
{
	smlAssert(alert);
	return alert->type;
}

const gchar*
smlSanAlertGetContentType (SmlSanAlert *alert)
{
	smlAssert(alert);
	return alert->contenttype;
}
const gchar*
smlSanAlertGetServerURI (SmlSanAlert *alert)
{
	smlAssert(alert);
	return alert->serverURI;
}
