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

#include "tests/support.h"

#include <libsyncml/objects/sml_auth.h>
#include <libsyncml/objects/sml_devinf_obj.h>

#define NUM_SESSIONS 30

typedef struct managerTracker {
	SmlManager *manager;
	SmlSession *session;
} managerTracker;

unsigned int transport_errors = 0;
unsigned int num_sessions = 0;
unsigned int num_finals = 0;
unsigned int num_end = 0;
unsigned int session_errors = 0;
int num_disconnects = 0;

unsigned int defaultMaxMsgSize = 10240;
unsigned int defaultMaxObjSize = 1024000;

static void _manager_event(SmlManager *manager, SmlManagerEventType type, SmlSession *session, SmlError *error, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p, %p, %p)", __func__, manager, type, session, error, userdata);
	managerTracker *tracker = userdata;
	smlAssert(manager);
	smlAssert(userdata);
	
	switch (type) {
		case SML_MANAGER_SESSION_FLUSH:
			break;
		case SML_MANAGER_CONNECT_DONE:
		case SML_MANAGER_SESSION_ESTABLISHED:
			break;
		case SML_MANAGER_DISCONNECT_DONE:
			num_disconnects++;
			break;
		case SML_MANAGER_TRANSPORT_ERROR:
			transport_errors++;
			break;
		case SML_MANAGER_SESSION_NEW:
			smlAssert(session);
			tracker->session = session;
			num_sessions++;
			smlSessionRef(session);
			break;
		case SML_MANAGER_SESSION_FINAL:
			num_finals++;
			break;
		case SML_MANAGER_SESSION_END:
			num_end++;
			break;
		case SML_MANAGER_SESSION_ERROR:
		case SML_MANAGER_SESSION_WARNING:
			session_errors++;
			break;
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

START_TEST (devinf_crash)
{
	transport_errors = 0;
	num_sessions = 0;
	num_finals = 0;
	num_end = 0;
	num_disconnects = 0;
	session_errors = 0;

	setup_testbed(NULL);
	
	SmlError *error = NULL;
	SmlTransport *server = smlTransportNew(SML_TRANSPORT_HTTP_SERVER, &error);
	SmlTransport *client = smlTransportNew(SML_TRANSPORT_HTTP_CLIENT, &error);
	
	sml_fail_unless(smlTransportSetConfigOption(client, "URL", "http://127.0.0.1:12030", &error), NULL);

	sml_fail_unless(smlTransportSetConfigOption(server, "PORT", "12030", &error), NULL);
	
	managerTracker *clienttracker = g_malloc0(sizeof(managerTracker));
	SmlManager *clientmanager = clienttracker->manager = smlManagerNew(client, &error);
	smlManagerSetEventCallback(clienttracker->manager, _manager_event, clienttracker);
	managerTracker *servertracker = g_malloc0(sizeof(managerTracker));
	SmlManager *servermanager = servertracker->manager = smlManagerNew(server, &error);
	smlManagerSetEventCallback(servertracker->manager, _manager_event, servertracker);
	smlManagerSetLocalMaxMsgSize(servermanager, defaultMaxMsgSize);
	smlManagerSetLocalMaxObjSize(servermanager, defaultMaxObjSize);
	
	sml_fail_unless(smlTransportInitialize(client, &error), "%s", smlErrorPrint(&error));
	sml_fail_unless(smlTransportInitialize(server, &error), "%s", smlErrorPrint(&error));

	sml_fail_unless(smlManagerStart(clientmanager, &error), NULL);
	sml_fail_unless(error == NULL, NULL);
	sml_fail_unless(smlManagerStart(servermanager, &error), NULL);
	sml_fail_unless(error == NULL, NULL);

	SmlLocation *loc = smlLocationNew("test", NULL, &error);
	sml_fail_unless(loc != NULL, NULL);
	sml_fail_unless(error == NULL, NULL);
	
	/* The authenticator */
	SmlAuthenticator *auth2 = smlAuthNew(&error);
	smlAuthSetEnable(auth2, FALSE);
	smlAuthRegister(auth2, servermanager, &error);
	
	/* The devinf obj for the server */
	SmlDevInf *devinf = smlDevInfNew("LibSyncmML", SML_DEVINF_DEVTYPE_WORKSTATION, &error);
	smlDevInfSetSupportsLargeObjs(devinf, TRUE);
	SmlDevInfAgent *agent = smlDevInfAgentNew(devinf, &error);
	smlDevInfAgentRegister(agent, servermanager, &error);

	/* And we also add the devinfo to the devinf agent */
	SmlDevInfDataStore *datastore = smlDevInfDataStoreNew(smlLocationGetURI(loc), &error);
	smlDevInfDataStoreSetRxPref(datastore, SML_ELEMENT_TEXT_VCARD, "2.1");
	smlDevInfDataStoreSetTxPref(datastore, SML_ELEMENT_TEXT_VCARD, "2.1");
	
	smlDevInfDataStoreSetSyncCap(datastore, SML_DEVINF_SYNCTYPE_TWO_WAY, TRUE);
	smlDevInfDataStoreSetSyncCap(datastore, SML_DEVINF_SYNCTYPE_SLOW_SYNC, TRUE);
	smlDevInfDataStoreSetSyncCap(datastore, SML_DEVINF_SYNCTYPE_SERVER_ALERTED_SYNC, TRUE);
	
	smlDevInfAddDataStore(devinf, datastore);

	smlLocationUnref(loc);
	
	const char *datastr = "<?xml version=\"1.0\"?><!DOCTYPE SyncML PUBLIC \"-//SYNCML//DTD SyncML 1.1//EN\" \"http://www.syncml.org/docs/syncml_represent_v11_20020213.dtd\"><SyncML xmlns=\"syncml:SYNCML1.1\"><SyncHdr><VerDTD>1.1</VerDTD><VerProto>SyncML/1.1</VerProto><SessionID>10</SessionID><MsgID>1</MsgID><Target><LocURI>PC Suite</LocURI></Target><Source><LocURI>IMEI:xxxxxxxxxxxxxxx</LocURI></Source><Meta><MaxMsgSize xmlns=\"syncml:metinf\">10000</MaxMsgSize></Meta></SyncHdr><SyncBody><Status><CmdID>1</CmdID><MsgRef>1</MsgRef><CmdRef>0</CmdRef><Cmd>SyncHdr</Cmd><TargetRef>/</TargetRef><SourceRef>PC Suite</SourceRef><Data>200</Data></Status><Status><CmdID>2</CmdID><MsgRef>1</MsgRef><CmdRef>1</CmdRef><Cmd>Alert</Cmd><SourceRef>Contacts</SourceRef><Data>200</Data></Status><Alert><CmdID>3</CmdID><Data>201</Data><Item><Target><LocURI>./Contacts</LocURI></Target><Source><LocURI>./C\\System\\Data\\Contacts.cdb</LocURI></Source><Meta><Anchor xmlns=\"syncml:metinf\"><Last></Last><Next>20060317T083025Z</Next></Anchor></Meta></Item></Alert><Put><CmdID>4</CmdID><Meta><Type xmlns=\"syncml:metinf\">application/vnd.syncml-devinf+xml</Type></Meta><Item><Source><LocURI>./devinf11</LocURI></Source><Data><DevInf xmlns=\"syncml:devinf\"><VerDTD>1.1</VerDTD><Man>NOKIA</Man><Mod>6680</Mod><SwV>2.04.15</SwV><DevID>IMEI:xxxxxxxxxxxxxxx</DevID><DevTyp>phone</DevTyp><SupportLargeObjs></SupportLargeObjs><SupportNumberOfChanges></SupportNumberOfChanges><DataStore><SourceRef>./C\\System\\Data\\Contacts.cdb</SourceRef><MaxGUIDSize>8</MaxGUIDSize><Rx-Pref><CTType>text/x-vcard</CTType><VerCT>2.1</VerCT></Rx-Pref><Rx><CTType>text/vcard</CTType><VerCT>3.0</VerCT></Rx><Tx-Pref><CTType>text/x-vcard</CTType><VerCT>2.1</VerCT></Tx-Pref><SyncCap><SyncType>1</SyncType><SyncType>2</SyncType><SyncType>3</SyncType><SyncType>4</SyncType><SyncType>5</SyncType><SyncType>6</SyncType><SyncType>7</SyncType></SyncCap></DataStore><CTCap><CTType>text/x-vcard</CTType><PropName>BEGIN</PropName><ValEnum>VCARD</ValEnum><PropName>END</PropName><ValEnum>VCARD</ValEnum><PropName>VERSION</PropName><ValEnum>2.1</ValEnum><PropName>REV</PropName><PropName>N</PropName><PropName>ADR</PropName><ParamName>HOME</ParamName><ParamName>WORK</ParamName><PropName>TEL</PropName><ParamName>HOME</ParamName><ParamName>WORK</ParamName><ParamName>CELL</ParamName><ParamName>PAGER</ParamName><ParamName>FAX</ParamName><ParamName>VIDEO</ParamName><PropName>FN</PropName><PropName>EMAIL</PropName><ParamName>INTERNET</ParamName><ParamName>HOME</ParamName><ParamName>WORK</ParamName><PropName>URL</PropName><ParamName>HOME</ParamName><ParamName>WORK</ParamName><PropName>NOTE</PropName><PropName>TITLE</PropName><PropName>ORG</PropName><PropName>PHOTO</PropName><PropName>BDAY</PropName><PropName>SOUND</PropName><ParamName>X-IRMC-N</ParamName><CTType>text/vcard</CTType><PropName>BEGIN</PropName><ValEnum>VCARD</ValEnum><PropName>END</PropName><ValEnum>VCARD</ValEnum><PropName>VERSION</PropName><ValEnum>3.0</ValEnum><PropName>REV</PropName><PropName>N</PropName><PropName>ADR</PropName><ParamName>HOME</ParamName><ParamName>WORK</ParamName><PropName>TEL</PropName><ParamName>HOME</ParamName><ParamName>WORK</ParamName><ParamName>CELL</ParamName><ParamName>PAGER</ParamName><ParamName>FAX</ParamName><ParamName>VIDEO</ParamName><PropName>FN</PropName><PropName>EMAIL</PropName><ParamName>INTERNET</ParamName><ParamName>HOME</ParamName><ParamName>WORK</ParamName><PropName>URL</PropName><ParamName>HOME</ParamName><ParamName>WORK</ParamName><PropName>NOTE</PropName><PropName>TITLE</PropName><PropName>ORG</PropName><PropName>PHOTO</PropName><PropName>BDAY</PropName><PropName>SOUND</PropName><ParamName>X-IRMC-N</ParamName></CTCap></DevInf></Data></Item></Put><Get><CmdID>5</CmdID><Meta><Type xmlns=\"syncml:metinf\">application/vnd.syncml-devinf+xml</Type></Meta><Item><Target><LocURI>./devinf11</LocURI></Target></Item></Get><Final></Final></SyncBody></SyncML>";
	
	SmlTransportData *data = smlTransportDataNew((char *)datastr, strlen(datastr), SML_MIMETYPE_XML, FALSE, &error);
	sml_fail_unless(data != NULL, NULL);
	sml_fail_unless(error == NULL, NULL);

	sml_fail_unless(smlTransportSend(client, NULL, data, &error), NULL);
	sml_fail_unless(error == NULL, NULL);
	
	smlTransportDataDeref(data);
	
	while (num_finals != 1) {
		smlManagerDispatch(servermanager);
		usleep(100);
	}
	
	SmlDevInf *devinf2 = smlDevInfAgentGetSessionDevInf(agent, servertracker->session);
	sml_fail_unless(devinf2 != NULL, NULL);
	
	sml_fail_unless(transport_errors == 0, NULL);
	sml_fail_unless(num_sessions == 1, NULL);
	sml_fail_unless(num_finals == 1, NULL);
	sml_fail_unless(num_end == 0, NULL);
	sml_fail_unless(session_errors == 1, NULL);
	
	/* stop the server */

	smlManagerStop(servermanager);
	while(num_disconnects < 1) {
		smlManagerDispatch(servermanager);
	}
	smlManagerFree(servermanager);
	smlAuthFree(auth2);

	/* stop the client */

	smlManagerStop(clientmanager);
	smlManagerFree(clientmanager);

	/* The clienttracker session was referenced by the manager callback. */
	smlSessionUnref(servertracker->session);

	smlSafeFree((gpointer *)&clienttracker);
	smlSafeFree((gpointer *)&servertracker);

	sml_fail_unless(smlTransportFinalize(server, &error), "%s", smlErrorPrint(&error));
	sml_fail_unless(smlTransportFinalize(client, &error), "%s", smlErrorPrint(&error));
	
	smlTransportFree(server);
	smlTransportFree(client);

	smlDevInfAgentFree(agent);
}
END_TEST

/* client -- devinf get --> server
 * server -- result --> client
 * client -- status --> server
 * server -- status --> client
 */
START_TEST (devinf_result)
{
	transport_errors = 0;
	num_sessions = 0;
	num_finals = 0;
	num_end = 0;
	num_disconnects = 0;
	session_errors = 0;
	
	setup_testbed(NULL);
	
	SmlError *error = NULL;
	SmlTransport *server = smlTransportNew(SML_TRANSPORT_HTTP_SERVER, &error);
	SmlTransport *client = smlTransportNew(SML_TRANSPORT_HTTP_CLIENT, &error);
	
	sml_fail_unless(smlTransportSetConfigOption(client, "URL", "http://127.0.0.1:12031", &error), NULL);

	sml_fail_unless(smlTransportSetConfigOption(server, "PORT", "12031", &error), NULL);
	
	managerTracker *clienttracker = g_malloc0(sizeof(managerTracker));
	SmlManager *clientmanager = clienttracker->manager = smlManagerNew(client, &error);
	smlManagerSetEventCallback(clienttracker->manager, _manager_event, clienttracker);
	managerTracker *servertracker = g_malloc0(sizeof(managerTracker));
	SmlManager *servermanager = servertracker->manager = smlManagerNew(server, &error);
	smlManagerSetEventCallback(servertracker->manager, _manager_event, servertracker);
	smlManagerSetLocalMaxMsgSize(servermanager, defaultMaxMsgSize);
	smlManagerSetLocalMaxObjSize(servermanager, defaultMaxObjSize);
	
	sml_fail_unless(smlTransportInitialize(client, &error), "%s", smlErrorPrint(&error));
	sml_fail_unless(smlTransportInitialize(server, &error), "%s", smlErrorPrint(&error));

	sml_fail_unless(smlManagerStart(clientmanager, &error), NULL);
	sml_fail_unless(error == NULL, NULL);
	sml_fail_unless(smlManagerStart(servermanager, &error), NULL);
	sml_fail_unless(error == NULL, NULL);

	SmlLocation *loc = smlLocationNew("test", NULL, &error);
	sml_fail_unless(loc != NULL, NULL);
	sml_fail_unless(error == NULL, NULL);
	
	SmlLocation *loc1 = smlLocationNew("test", NULL, &error);
	sml_fail_unless(loc1 != NULL, NULL);
	sml_fail_unless(error == NULL, NULL);
	
	/* The devinf obj */
	SmlDevInf *devinf = smlDevInfNew("LibSyncmML", SML_DEVINF_DEVTYPE_WORKSTATION, &error);
	SmlDevInfAgent *clientagent = smlDevInfAgentNew(devinf, &error);
	smlDevInfAgentRegister(clientagent, clientmanager, &error);

	/* And we also add the devinfo to the devinf agent */
	SmlDevInfDataStore *datastore = smlDevInfDataStoreNew(smlLocationGetURI(loc1), &error);
	smlDevInfDataStoreSetRxPref(datastore, SML_ELEMENT_TEXT_VCARD, "2.1");
	smlDevInfDataStoreSetTxPref(datastore, SML_ELEMENT_TEXT_VCARD, "2.1");
	smlDevInfDataStoreSetSyncCap(datastore, SML_DEVINF_SYNCTYPE_TWO_WAY, TRUE);
	smlDevInfDataStoreSetSyncCap(datastore, SML_DEVINF_SYNCTYPE_SLOW_SYNC, TRUE);
	smlDevInfDataStoreSetSyncCap(datastore, SML_DEVINF_SYNCTYPE_SERVER_ALERTED_SYNC, TRUE);
	smlDevInfAddDataStore(devinf, datastore);
	
	devinf = smlDevInfNew("LibSyncmML", SML_DEVINF_DEVTYPE_SERVER, &error);
	smlDevInfSetSupportsLargeObjs(devinf, TRUE);
	SmlDevInfAgent *serveragent = smlDevInfAgentNew(devinf, &error);
	smlDevInfAgentRegister(serveragent, servermanager, &error);
	
	datastore = smlDevInfDataStoreNew(smlLocationGetURI(loc1), &error);
	smlDevInfDataStoreSetRxPref(datastore, SML_ELEMENT_TEXT_VCARD, "2.1");
	smlDevInfDataStoreSetTxPref(datastore, SML_ELEMENT_TEXT_VCARD, "2.1");
	smlDevInfDataStoreSetSyncCap(datastore, SML_DEVINF_SYNCTYPE_TWO_WAY, TRUE);
	smlDevInfDataStoreSetSyncCap(datastore, SML_DEVINF_SYNCTYPE_SLOW_SYNC, TRUE);
	smlDevInfDataStoreSetSyncCap(datastore, SML_DEVINF_SYNCTYPE_SERVER_ALERTED_SYNC, TRUE);
	smlDevInfAddDataStore(devinf, datastore);
	
	smlLocationUnref(loc1);
	
	/* The authenticator */
	SmlAuthenticator *auth = smlAuthNew(&error);
	smlAuthSetEnable(auth, FALSE);
	smlAuthRegister(auth, clientmanager, &error);
	
	/* The authenticator */
	SmlAuthenticator *auth2 = smlAuthNew(&error);
	smlAuthSetEnable(auth2, FALSE);
	smlAuthRegister(auth2, servermanager, &error);
	
	clienttracker->session = smlSessionNew(SML_SESSION_TYPE_CLIENT, SML_MIMETYPE_XML, SML_VERSION_11, SML_PROTOCOL_SYNCML, loc, loc, NULL, 0, &error);
	
	sml_fail_unless(smlManagerSessionAdd(clientmanager, clienttracker->session, NULL, &error), NULL);
	sml_fail_unless(error == NULL, NULL);
	
	smlLocationUnref(loc);
	
	sml_fail_unless(smlDevInfAgentRequestDevInf(clientagent, clienttracker->session, &error), NULL);
	sml_fail_unless(error == NULL, NULL);
	sml_fail_unless(smlDevInfAgentSendDevInf(clientagent, clienttracker->session, &error), NULL);
	sml_fail_unless(error == NULL, NULL);
	
	sml_fail_unless(smlSessionFlush(clienttracker->session, TRUE, &error), NULL);
	sml_fail_unless(error == NULL, NULL);
	
	while (num_sessions != 2 || !smlDevInfAgentGetSessionDevInf(serveragent, servertracker->session)) {
		smlManagerDispatch(servermanager);
		smlManagerDispatch(clientmanager);
		usleep(100);
	}
	
	SmlDevInf *recvClientDevInf = smlDevInfAgentGetSessionDevInf(serveragent, servertracker->session);
	sml_fail_unless(recvClientDevInf != NULL, NULL);
	sml_fail_unless(smlDevInfNumDataStores(recvClientDevInf) == 1, NULL);
	
	sml_fail_unless(servertracker->session != NULL, NULL);
	sml_fail_unless(smlSessionFlush(servertracker->session, TRUE, &error), NULL);
	
	while (!smlDevInfAgentGetSessionDevInf(clientagent, clienttracker->session)) {
		smlManagerDispatch(servermanager);
		smlManagerDispatch(clientmanager);
		usleep(100);
	}
	
	SmlDevInf *recvServerDevInf = smlDevInfAgentGetSessionDevInf(clientagent, clienttracker->session);
	sml_fail_unless(recvServerDevInf != NULL, NULL);
	sml_fail_unless(smlDevInfNumDataStores(recvServerDevInf) == 1, NULL);
	
	sml_fail_unless(servertracker->session != NULL, NULL);
	sml_fail_unless(smlSessionEnd(clienttracker->session, &error), NULL);
	sml_fail_unless(error == NULL, NULL);
	
	while (num_end != 2 || num_finals != 4) {
		smlManagerDispatch(servermanager);
		smlManagerDispatch(clientmanager);
		usleep(100);
	}
	
	/* The tracker sessions were referenced by the manager callback.
	 * The clienttracker session is referenced twice because it
	 * was created by the testcase itself.
	 */
	smlSessionUnref(servertracker->session);
	smlSessionUnref(clienttracker->session);
	smlSessionUnref(clienttracker->session);
	
	sml_fail_unless(num_sessions == 2, NULL);
	sml_fail_unless(num_finals == 4, NULL);
	sml_fail_unless(num_end == 2, NULL);
	sml_fail_unless(session_errors == 0, NULL);
	
	smlAuthFree(auth);
	smlAuthFree(auth2);
	
	smlSafeFree((gpointer *)&clienttracker);
	smlSafeFree((gpointer *)&servertracker);
	
	smlManagerStop(clientmanager);
	smlManagerStop(servermanager);

	while(num_disconnects < 2) {
		smlManagerDispatch(clientmanager);
		smlManagerDispatch(servermanager);
        }
	
	smlManagerFree(clientmanager);
	smlManagerFree(servermanager);
	
	sml_fail_unless(smlTransportFinalize(server, &error), "%s", smlErrorPrint(&error));
	sml_fail_unless(smlTransportFinalize(client, &error), "%s", smlErrorPrint(&error));
	
	smlTransportFree(server);
	smlTransportFree(client);

	smlDevInfAgentFree(clientagent);
	smlDevInfAgentFree(serveragent);
}
END_TEST

/* SyncML 1.1 uses Size in CTCap Property.
 * SyncML 1.2 uses MaxSize in CTCap Property.
 * The CTCap parser must support both.
 */
START_TEST (devinf_max_size_element)
{
	SmlError *error = NULL;
	SmlDevInfCTCapType type;

	/* SyncML 1.1 */
	type = smlDevInfCTCapTypeFromString(SML_ELEMENT_SIZE, &error);
	sml_fail_unless(type == SML_DEVINF_CTCAP_SIZE, NULL);

	/* SyncML 1.1 and 1.2 */
	type = smlDevInfCTCapTypeFromString(SML_ELEMENT_MAXSIZE, &error);
	sml_fail_unless(type == SML_DEVINF_CTCAP_MAXSIZE, NULL);
}
END_TEST

@SML_TESTCASE_CODE@

