/**@@@+++@@@@******************************************************************
**
** Microsoft Windows Media
** Copyright (C) Microsoft Corporation. All rights reserved.
**
***@@@---@@@@******************************************************************
*/

// #include <stdlib.h>

#include "drmcommon.h"
#include "drmlicense.h"
#include "drmutilities.h"
#include "drmcontextsizes.h"
#include "drmmanager.h"

#include "drmsecureclock.h"

#include "tclib.h"
#include "tOEMIMP.h"
#include "tstutils.h"


static DRM_MANAGER_CONTEXT *g_pManagerContext = NULL;
DRMSYSTEMTIME *g_pTCRestoreTime = NULL;

DRM_RESULT Test_API_MGR_Initialize(long argc, char **argv)
{
	DRM_RESULT dr;
	DRM_CONST_STRING wszStorePath;

	ChkArg(!g_pManagerContext);
	ChkMem(g_pManagerContext = (DRM_MANAGER_CONTEXT*)OEM_malloc(sizeof(DRM_MANAGER_CONTEXT)));
	tGetDeviceStorePathname(&wszStorePath);
	ChkDR(DRM_MGR_Initialize(g_pManagerContext, &wszStorePath));
ErrorExit:
	return dr;
}

static DRM_RESULT iGetSecStorePassword(DRM_BYTE *rgbPassword)
{
	DRM_RESULT dr;
	DRM_BB_CONTEXT *pBlackboxContext = NULL;
	DRM_BYTE rgbBuffer[MAX_DEVICE_CERT_SIZE];    
    DRM_CONST_STRING dstrDevCert;

    DSTR_FROM_PB( &dstrDevCert, rgbBuffer, SIZEOF(rgbBuffer) );

	/* Get devcert to open the blackbox */
	ChkMem(pBlackboxContext = (DRM_BB_CONTEXT*)DX_VOS_MemMalloc(sizeof(DRM_BB_CONTEXT)));
    ChkDR( DRM_DDC_GetDeviceCertificate( (DRM_STRING*)&dstrDevCert, 0, &pBlackboxContext->CryptoContext ) );
    ChkDR( DRM_DCP_LoadPropertiesCache( &dstrDevCert, &pBlackboxContext->cachedCertValues, &pBlackboxContext->CryptoContext ) );
	ChkDR(DRM_BBX_Initialize(pBlackboxContext));

	ChkDR(DRM_SST_CreateGlobalStorePassword(rgbPassword, (DRM_BYTE*)pBlackboxContext));
ErrorExit:
	if (pBlackboxContext)
		DRM_BBX_Shutdown(pBlackboxContext);
	DX_VOS_MemFree(pBlackboxContext);	
	return dr;
}


static DRM_RESULT _SetSecureClockData(DRM_SECSTORE_CLOCK_DATA *pClock, DRM_HDS_CONTEXT *pHDS)
{
	DRM_RESULT dr;
	DRM_SECSTORE_CONTEXT *pSecStoreContext = NULL;
	DRM_BYTE rgbSecStoreInPswd[SHA_DIGEST_LEN];

	if (!pHDS) {
		ChkArg(g_pManagerContext);
		pHDS = &((DRM_MANAGER_CONTEXT_INTERNAL*)g_pManagerContext)->oHdsContext;
	}
	
	ChkMem(pSecStoreContext = (DRM_SECSTORE_CONTEXT*)OEM_malloc(sizeof(DRM_SECSTORE_CONTEXT)));
	ChkDR(iGetSecStorePassword(rgbSecStoreInPswd));
	ChkDR( DRM_SST_SetData( pSecStoreContext,
                            &g_idSData,
                            &g_idSDataPrev,
                            rgbSecStoreInPswd,
                            SECURE_STORE_GLOBAL_DATA,
                            pHDS,
                            (DRM_BYTE*)pClock, 
                            sizeof(DRM_SECSTORE_CLOCK_DATA) ) );

ErrorExit:
	OEM_free(pSecStoreContext);
	return dr;
}

/*	Sets the secure clock data directly
	argv[0]: validity flag
*/
DRM_RESULT Test_SetSecureClock(long argc, char **argv)
{
	DRM_RESULT dr;
	DRM_SECSTORE_CLOCK_DATA oClock;
    DRMSYSTEMTIME oSystemTime;
	DRM_HDS_CONTEXT *pHDSContext = NULL;
	DRM_CONST_STRING dstrHdsPath;

	ChkArg(argc > 0 && argv[0]);
	
	oClock.flag = (DRM_WORD)OEM_atol(argv[0]);

    OEM_GetDeviceDateTime(&oSystemTime);
    ChkArg(OEM_SystemTimeToFileTime(&oSystemTime, (DRMFILETIME*)&(oClock.LastKnownRealtimeSecureClock)));
    
	tGetDeviceStorePathname(&dstrHdsPath);
	ChkDR(OpenHDS(&pHDSContext, dstrHdsPath.pwszString, FALSE));

	ChkDR(_SetSecureClockData(&oClock, pHDSContext));

ErrorExit:
	CloseHDS(pHDSContext);
	return dr;
}


#if DRM_SUPPORT_SECURE_CLOCK
static DRM_RESULT _GetSecureClockData(DRM_SECSTORE_CLOCK_DATA *pClock)
{
	DRM_RESULT dr;
	DRM_SECSTORE_CONTEXT *pSecStoreContext = NULL;
	DRM_BYTE rgbSecStoreInPswd[SHA_DIGEST_LEN];
	DRM_DWORD cbClock;

	ChkArg(pClock && g_pManagerContext);
	ChkMem(pSecStoreContext = (DRM_SECSTORE_CONTEXT*)OEM_malloc(sizeof(DRM_SECSTORE_CONTEXT)));
	ChkDR(iGetSecStorePassword(rgbSecStoreInPswd));
	
	cbClock = sizeof(DRM_SECSTORE_CLOCK_DATA);
	ChkDR(DRM_SST_GetData(pSecStoreContext,
						&g_idSData,
						&g_idSDataPrev,
						rgbSecStoreInPswd,
						SECURE_STORE_GLOBAL_DATA,
						&((DRM_MANAGER_CONTEXT_INTERNAL*)g_pManagerContext)->oHdsContext,
						(DRM_BYTE*)pClock, 
						&cbClock));

ErrorExit:
	OEM_free(pSecStoreContext);
	return dr;
}
#endif
#if DRM_SUPPORT_SECURE_CLOCK
static DRM_RESULT _VerifyTIDInChallenge(DRM_ANSI_STRING *pdastrChallenge)
{
	DRM_RESULT dr;
	DRM_SECSTORE_CLOCK_DATA oClock;
	DRM_DWORD cb = pdastrChallenge->cchString;
	DRM_CONST_STRING dstr   = EMPTY_DRM_STRING;
	DRM_SUBSTRING    dasstr = { 0 };
	DRM_WCHAR       *pStart = NULL, 
	                *pStop  = NULL;
	
	dasstr.m_ich = 0;
	dasstr.m_cch = pdastrChallenge->cchString;
	
	/* Decode the challenge string*/
	ChkDR(DRM_B64_DecodeA(pdastrChallenge->pszString, &dasstr, &cb, NULL, DRM_BASE64_DECODE_IN_PLACE));
	
	dstr.pwszString = (DRM_WCHAR *) (pdastrChallenge->pszString + dasstr.m_ich);
	((DRM_WCHAR*)dstr.pwszString)[cb] = 0;

	/* Get the TID within <TID></TID> */
	ChkArg(pStart = OEM_wcsstr(dstr.pwszString, L"<TID>"));
	pStart += 5;
	dstr.pwszString = pStart;
	ChkArg(pStop = OEM_wcsstr(pStart, L"</TID>"));
	dstr.cchString = pStop - pStart;

	/* Decode the TID string */
	cb = dstr.cchString;
	ChkDR(DRM_B64_DecodeW(&dstr, &cb, NULL, DRM_BASE64_DECODE_IN_PLACE));
	ChkDR(_GetSecureClockData(&oClock));

	ChkArg(cb == sizeof(DRM_TID) && !DX_VOS_MemCmp(&(oClock.tid), dstr.pwszString, cb));
ErrorExit:
	return dr;
}

#endif
/*	Test API MGR_ClkGenerateChallenge
	argv[0]: status of DRM manager context: NULL or NORMAL
	argv[1]: Check returned URL? TRUE or FALSE
	argv[2]: status of the return challenge: NORMAL or NULL
*/
DRM_RESULT Test_API_MGR_ClkGenerateChallenge(long argc, char **argv)
{
#if DRM_SUPPORT_SECURE_CLOCK
	DRM_RESULT dr;
	DRM_CONST_STRING wszURL = {0}, wszURLFromDevcert;
	DRM_ANSI_STRING szChallenge = {0};

	ChkArg(argc == 3 && argv[1]);
	dr = DRM_MGR_ClkGenerateChallenge(argv[0]? g_pManagerContext: NULL,
		NULL, argv[1]? &wszURL.cchString: NULL,
		NULL, argv[2]? &szChallenge.cchString: NULL);
	if (dr != DRM_E_BUFFERTOOSMALL) {
		ChkDR(dr);
	}

	if (argv[1]) {
		ChkMem(wszURL.pwszString = (DRM_WCHAR*)OEM_malloc(wszURL.cchString * sizeof(DRM_WCHAR)));
	}
	if (argv[2]) {
		ChkMem(szChallenge.pszString = (DRM_CHAR*)OEM_malloc(szChallenge.cchString * sizeof(DRM_CHAR)));
	}
	ChkDR(DRM_MGR_ClkGenerateChallenge(argv[0]? g_pManagerContext: NULL,
		(DRM_WCHAR*)wszURL.pwszString, argv[1]? &wszURL.cchString: NULL,
		(DRM_BYTE*)szChallenge.pszString, argv[2]? &szChallenge.cchString: NULL));

	if (!DX_VOS_StrCmp(argv[1], "TRUE")) { /* Verify the returned secure clock service URL */
        DRM_BYTE rgbBuffer[MAX_DEVICE_CERT_SIZE];    
        DRM_CONST_STRING dstrDevCert;

        ChkArg(wszURL.pwszString);
        DSTR_FROM_PB( &dstrDevCert, rgbBuffer, SIZEOF(rgbBuffer) );
        ChkDR( DRM_DDC_GetDeviceCertificate((DRM_STRING*)&dstrDevCert, 
                                             0,
                                            &((DRM_MANAGER_CONTEXT_INTERNAL*)g_pManagerContext)->oBlackBoxContext.CryptoContext) );

		ChkDR(DRM_DCP_GetAttribute(&dstrDevCert, DRM_DEVCERT_SECURECLOCKURL, NULL, &wszURLFromDevcert));
		
		/* The returned URL includes a NULL terminating char. */
		if (wszURL.cchString > wszURLFromDevcert.cchString)
			wszURL.cchString = wszURLFromDevcert.cchString;

		if (!DRM_UTL_DSTRStringsEqual(&wszURL, &wszURLFromDevcert)) {
			Log("Trace", "The returned SecureClock URL doesn't match the one in devcert");
			ChkDR(DRM_E_FAIL);
		}
	}

	/* Verify TID in the challenge string matches the one in the secstore */
	ChkDR(_VerifyTIDInChallenge(&szChallenge));
ErrorExit:
	OEM_free((DRM_WCHAR*)wszURL.pwszString);
	OEM_free(szChallenge.pszString);
	return dr;
#else
	return DRM_E_NOTIMPL;
#endif
}

/* Sets up the secure clock data after a challenge.
	argv[0]: TID in base64 string
	argv[1]: Challenge time in base64 string
*/
DRM_RESULT Test_SetChallengeState(long argc, char **argv)
{
	DRM_RESULT dr;
	DRM_SECSTORE_CLOCK_DATA oClock;
	DRM_SUBSTRING           dasstrData;
	DRM_DWORD cb;
	DRMFILETIME ft;
	DRMSYSTEMTIME st,UpdatedTime;

	ChkArg(argc == 2 && argv[0] && argv[1]);

	DX_VOS_MemSet(&oClock, 0, sizeof(DRM_SECSTORE_CLOCK_DATA));

	/* Set TID */
	cb = sizeof(DRM_TID);
	dasstrData.m_ich = 0;
	dasstrData.m_cch = DX_VOS_StrLen(argv [0]);
	
	ChkDR(DRM_B64_DecodeA(argv [0], &dasstrData, &cb, (DRM_BYTE *) &oClock.tid, 0));

	/* Set Challenge time */
	cb = sizeof(DRM_UINT64);
	dasstrData.m_ich = 0;
	dasstrData.m_cch = DX_VOS_StrLen(argv [1]);
	
	ChkDR(DRM_B64_DecodeA(argv [1], &dasstrData, &cb, (DRM_BYTE *) &oClock.ChallengeTime, 0));

	oClock.flag = CLK_TID_VALID | CLK_CHALLENGETIME_VALID;

	ChkDR(_SetSecureClockData(&oClock, NULL));

	/*	Before changing the test machine clock, we need to remember the current time. 
		PostTestCase will restore the machine clock. */
	if (!g_pTCRestoreTime) {
		ChkMem(g_pTCRestoreTime = (DRMSYSTEMTIME*)OEM_malloc(sizeof(DRMSYSTEMTIME)));
		OEM_GetDeviceDateTime(g_pTCRestoreTime);
	}

	/* Set the machine clock to the challenge time */
	UI64_TO_FILETIME(oClock.ChallengeTime, ft);
	ChkArg(OEM_FileTimeToSystemTime(&ft, &st));
	OEM_SetSystemTime(&st);
	OEM_GetDeviceDateTime(&UpdatedTime);
	/*The following if statement is to make sure the system time is updated correctly
	even when we cross between the DST boundaries (when the current date is between
	October and April)*/
	if(UpdatedTime.wHour != st.wHour)
	{
		OEM_SetSystemTime(&st);
	}

ErrorExit:
	return dr;
}

/*	Test API MGR_ClkProcessResponse
	argv[0]: response file name
	argv[1]: status of drm manager context, NORMAL or NULL
	argv[2]: status of response string, NORMAL or NULL
	argv[3]: expected return code from the response or NULL
	argv[4]: optional flag to modify the response: BREAK_SIGNATURE
*/
DRM_RESULT Test_API_MGR_ClkProcessResponse(long argc, char **argv)
{
#if DRM_SUPPORT_SECURE_CLOCK
	DRM_RESULT dr;
	DRM_DWORD cbData;
	DRM_BYTE *pbData = NULL;
	DRM_RESULT drResult;

	ChkArg(argc >= 4);
	ChkArg(LoadTestFile(NULL, argv[0], &pbData, &cbData));

	if (argc > 4 && argv[4]) {
		if (!DX_VOS_StrCmp(argv[4], "BREAK_CERT")) {
			pbData[cbData / 2]++;
		} else if (!DX_VOS_StrCmp(argv[4], "BREAK_SIGNATURE")) {
			ChkArg(cbData > 248);
			pbData[248]++;
		}
	}

	ChkDR(DRM_MGR_ClkProcessResponse(argv[1]? g_pManagerContext: NULL,
		argv[2]? pbData: NULL, cbData,
		argv[3]? &drResult: NULL));

	if (argv[3] && (drResult != (DRM_RESULT)OEM_atol(argv[3]))) {
		Log("Trace", "Secure time response code is %d. Expecting %s", drResult, argv[2]);
		dr = DRM_S_FALSE;
	}
ErrorExit:
	OEM_free(pbData);
	return dr;
#else
	return DRM_E_NOTIMPL;
#endif
}

/*
	argv[0]: amount to change the machine clock in minutes
*/
DRM_RESULT Test_ChangeClock(long argc, char **argv)
{
	DRM_RESULT dr = DRM_SUCCESS;

	ChkArg(argc == 1 && argv[0]);
	ChkDR(tChangeSystemTime(OEM_atol(argv[0]) * 60));
ErrorExit:
	return dr;
}

/*	Test API MGR_GetSecureClock
	argv[0]: status of drm manager context, NORMAL or NULL
	argv[1]: status of the return string data, NORMAL or NULL
	argv[2]: status of the return xml data, NORMAL or NULL
*/
DRM_RESULT Test_API_MGR_GetSecureClock(long argc, char **argv)
{
#if DRM_SUPPORT_SECURE_CLOCK
	DRM_RESULT dr;
	DRM_WCHAR *wszString = NULL;
	DRM_BYTE *pbXMLData = NULL;
	DRM_DWORD cchString, cbXMLData, dwFlag;

	ChkArg(argc == 3);

	dr = DRM_MGR_GetSecureClock(argv[0]? g_pManagerContext: NULL,
		NULL, argv[1]? &cchString: NULL, argv[1]? &dwFlag: NULL,
		NULL, argv[2]? &cbXMLData: NULL);
	if (dr != DRM_E_BUFFERTOOSMALL) {
		ChkDR(dr);
	}
	if (argv[1]) {
		ChkMem(wszString = (DRM_WCHAR*)OEM_malloc(cchString * sizeof(DRM_WCHAR)));
	}
	if (argv[2]) {
		ChkMem(pbXMLData = (DRM_BYTE*)OEM_malloc(cbXMLData * sizeof(DRM_BYTE)));
	}
	ChkDR(DRM_MGR_GetSecureClock(argv[0]? g_pManagerContext: NULL,
		argv[1]? wszString: NULL, argv[1]? &cchString: NULL, argv[1]? &dwFlag: NULL,
		argv[2]? pbXMLData: NULL, argv[2]? &cbXMLData: NULL));

ErrorExit:
	OEM_free(wszString);
	OEM_free(pbXMLData);
	return dr;
#else
	return DRM_E_NOTIMPL;
#endif
}

DRM_RESULT SCLK_PreTestCase(long lTCID, char *strTCName)
{
	const DRM_WCHAR devCertTemplate[] = { TWO_BYTES('d','e'), TWO_BYTES('v','c'),
									 	  TWO_BYTES('e','r'), TWO_BYTES('t','t'),
										  TWO_BYTES('e','m'), TWO_BYTES('p','l'),
										  TWO_BYTES('a','t'), TWO_BYTES('e','.'),
										  TWO_BYTES('d','a'), TWO_BYTES('t','\0') };
	const DRM_WCHAR priv[] = { TWO_BYTES('p','r'), TWO_BYTES('i','v'), TWO_BYTES('.','d'),
							   TWO_BYTES('a','t'), TWO_BYTES('\0',0) };

	RemoveDRMFile(RMFILE_STORE);
	g_pManagerContext = NULL;

	return SetDeviceEnv(devCertTemplate, priv, TRUE);
}

DRM_RESULT SCLK_PostTestCase(long lTCID, char *strTCName)
{
	if (g_pTCRestoreTime) { /* Restore the test machine clock */
		OEM_SetSystemTime(g_pTCRestoreTime);
		OEM_free(g_pTCRestoreTime);
		g_pTCRestoreTime = NULL;
	}

	if (g_pManagerContext) {
		DRM_MGR_Uninitialize(g_pManagerContext);
		g_pManagerContext = NULL;
	}
	
	return DRM_SUCCESS;
}

/*
IMPLEMENT_DEFAULT_WARPTEST

BEGIN_APIMAP(RefSecureClockTest_ansi, "secclock")
	API_ENTRY(Test_API_MGR_Initialize)
	API_ENTRY(Test_API_MGR_ClkGenerateChallenge)
	API_ENTRY(Test_API_MGR_ClkProcessResponse)
	API_ENTRY(Test_API_MGR_GetSecureClock)
	API_ENTRY(Test_SetChallengeState)
	API_ENTRY(Test_SetSecureClock)
	API_ENTRY(Test_ChangeClock)
END_APIMAP
*/
