/*
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 * 
 * The Original Code is the Netscape security libraries.
 * 
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are 
 * Copyright (C) 1998-2000 Netscape Communications Corporation.  All
 * Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 */

#include "seccomon.h"
#include "cert.h"
#include "secutil.h"
#include "nspr.h"
#include "nss.h"
#include "blapi.h"
#include "plgetopt.h"
#include "lowkeyi.h"


#define MAX_RSA_MODULUS_BYTES (1024/8)
#define DEFAULT_ITERS 10

extern NSSLOWKEYPrivateKey * getDefaultRSAPrivateKey(void);
extern NSSLOWKEYPublicKey  * getDefaultRSAPublicKey(void);

typedef struct TimingContextStr TimingContext;

struct TimingContextStr {
    int64 start;
    int64 end;
    int64 interval;

    long  days;     
    int   hours;    
    int   minutes;  
    int   seconds;  
    int   millisecs;
};

TimingContext *CreateTimingContext(void) {
    return PR_Malloc(sizeof(TimingContext));
}

void DestroyTimingContext(TimingContext *ctx) {
    PR_Free(ctx);
}

void TimingBegin(TimingContext *ctx) {
    ctx->start = PR_Now();
}

static void timingUpdate(TimingContext *ctx) {
    int64 tmp, remaining;
    int64 L1000,L60,L24;

    LL_I2L(L1000,1000);
    LL_I2L(L60,60);
    LL_I2L(L24,24);

    LL_DIV(remaining, ctx->interval, L1000);
    LL_MOD(tmp, remaining, L1000);
    LL_L2I(ctx->millisecs, tmp);
    LL_DIV(remaining, remaining, L1000);
    LL_MOD(tmp, remaining, L60);
    LL_L2I(ctx->seconds, tmp);
    LL_DIV(remaining, remaining, L60);
    LL_MOD(tmp, remaining, L60);
    LL_L2I(ctx->minutes, tmp);
    LL_DIV(remaining, remaining, L60);
    LL_MOD(tmp, remaining, L24);
    LL_L2I(ctx->hours, tmp);
    LL_DIV(remaining, remaining, L24);
    LL_L2I(ctx->days, remaining);
}

void TimingEnd(TimingContext *ctx) {
    ctx->end = PR_Now();
    LL_SUB(ctx->interval, ctx->end, ctx->start);
    PORT_Assert(LL_GE_ZERO(ctx->interval));
    timingUpdate(ctx);
}

void TimingDivide(TimingContext *ctx, int divisor) {
    int64 tmp;

    LL_I2L(tmp, divisor);
    LL_DIV(ctx->interval, ctx->interval, tmp);

    timingUpdate(ctx);
}

char *TimingGenerateString(TimingContext *ctx) {
    char *buf = NULL;

    if (ctx->days != 0) {
	buf = PR_sprintf_append(buf, "%d days", ctx->days);
    }
    if (ctx->hours != 0) {
	if (buf != NULL) buf = PR_sprintf_append(buf, ", ");
	buf = PR_sprintf_append(buf, "%d hours", ctx->hours);
    }
    if (ctx->minutes != 0) {
	if (buf != NULL) buf = PR_sprintf_append(buf, ", ");
	buf = PR_sprintf_append(buf, "%d minutes", ctx->minutes);
    }
    if (buf != NULL) buf = PR_sprintf_append(buf, ", and ");
    if (!buf && ctx->seconds == 0) {
	int interval;
	LL_L2I(interval, ctx->interval);
    	if (ctx->millisecs < 100) 
	    buf = PR_sprintf_append(buf, "%d microseconds", interval);
	else
	    buf = PR_sprintf_append(buf, "%d milliseconds", ctx->millisecs);
    } else if (ctx->millisecs == 0) {
	buf = PR_sprintf_append(buf, "%d seconds", ctx->seconds);
    } else {
	buf = PR_sprintf_append(buf, "%d.%03d seconds",
				ctx->seconds, ctx->millisecs);
    }
    return buf;
}

void
Usage(char *progName)
{
    fprintf(stderr, "Usage: %s [-d certdir] [-i iterations] [-s | -e]"
	            " -n nickname\n",
	    progName);
    fprintf(stderr, "%-20s Cert database directory (default is ~/.netscape)\n",
	    "-d certdir");
    fprintf(stderr, "%-20s How many operations to perform\n", "-i iterations");
    fprintf(stderr, "%-20s Perform signing (private key) operations\n", "-s");
    fprintf(stderr, "%-20s Perform encryption (public key) operations\n", "-e");
    fprintf(stderr, "%-20s Nickname of certificate or key\n", "-n nickname");
    exit(-1);
}

void
printCount(int iterations)
{

}


static void
dumpBytes( unsigned char * b, int l)
{
    int i;
    if (l <= 0)
        return;
    for (i = 0; i < l; ++i) {
        if (i % 16 == 0)
            printf("\t");
        printf(" %02x", b[i]);
        if (i % 16 == 15)
            printf("\n");
    }
    if ((i % 16) != 0)
        printf("\n");
}

static void
dumpItem( SECItem * item, const char * description)
{
    if (item->len & 1 && item->data[0] == 0) {
	printf("%s: (%d bytes)\n", description, item->len - 1);
	dumpBytes(item->data + 1, item->len - 1);
    } else {
	printf("%s: (%d bytes)\n", description, item->len);
	dumpBytes(item->data, item->len);
    }
}

void
printPrivKey(NSSLOWKEYPrivateKey * privKey)
{
    RSAPrivateKey *rsa = &privKey->u.rsa;

    dumpItem( &rsa->modulus, 		"n");
    dumpItem( &rsa->publicExponent, 	"e");
    dumpItem( &rsa->privateExponent, 	"d");
    dumpItem( &rsa->prime1, 		"P");
    dumpItem( &rsa->prime2, 		"Q");
    dumpItem( &rsa->exponent1, 	        "d % (P-1)");
    dumpItem( &rsa->exponent2, 	        "d % (Q-1)");
    dumpItem( &rsa->coefficient, 	"(Q ** -1) % P");
    puts("");
}

void
printFnCounts(long doPrint)
{

}

typedef SECStatus (* RSAOp)(void *               key, 
			    unsigned char *      output,
		            unsigned char *      input);


int
main(int argc, char **argv)
{
    TimingContext *	  timeCtx;
    SECKEYPublicKey *	  pubHighKey;
    NSSLOWKEYPrivateKey * privKey;
    NSSLOWKEYPublicKey *  pubKey;
    CERTCertificate *	  cert;
    char *		  progName;
    char *		  secDir 	= NULL;
    char *		  nickname 	= NULL;
    RSAOp                 fn;
    void *                rsaKey;
    PLOptState *          optstate;
    PLOptStatus           optstatus;
    long 		  iters 	= DEFAULT_ITERS;
    int		 	  i;
    PRBool 		  doPriv 	= PR_FALSE;
    PRBool 		  doPub 	= PR_FALSE;
    int 		  rv;
    CERTCertDBHandle 	* certdb;
    unsigned char 	  buf[1024];
    unsigned char 	  buf2[1024];

    progName = strrchr(argv[0], '/');
    if (!progName)
	progName = strrchr(argv[0], '\\');
    progName = progName ? progName+1 : argv[0];

    optstate = PL_CreateOptState(argc, argv, "d:i:sen:");
    while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
	switch (optstate->option) {
	case '?':
	    Usage(progName);
	    break;
	case 'd':
	    secDir = PORT_Strdup(optstate->value);
	    break;
	case 'i':
	    iters = atol(optstate->value);
	    break;
	case 's':
	    doPriv = PR_TRUE;
	    break;
	case 'e':
	    doPub = PR_TRUE;
	    break;
	case 'n':
	    nickname = PORT_Strdup(optstate->value);
	    break;
	}
    }
    if (optstatus == PL_OPT_BAD)
	Usage(progName);

    if ((doPriv && doPub) || (nickname == NULL)) Usage(progName);

    if (!doPriv && !doPub) doPriv = PR_TRUE;

    PR_Init( PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);

    secDir = SECU_ConfigDirectory(secDir);
    if (strcmp(nickname, "none")) {
	rv = NSS_Init(secDir);
	if (rv != SECSuccess) {
	    fprintf(stderr, "NSS_Init failed.\n");
	    exit(1);
	}
	certdb = CERT_GetDefaultCertDB();
    } else {
	rv = NSS_NoDB_Init(secDir);
	if (rv != SECSuccess) {
	    fprintf(stderr, "NSS_NoDB_Init failed.\n");
	    exit(1);
	}
    }
#if defined(SECU_GetPassword)
    if (doPub) {
	if (!strcmp(nickname, "none")) {
	    pubKey = getDefaultRSAPublicKey();
	} else {
	    cert = CERT_FindCertByNickname(certdb, nickname);
	    if (cert == NULL) {
		fprintf(stderr,
			"Can't find certificate by name \"%s\"\n", nickname);
		exit(1);
	    }
	    pubHighKey = CERT_ExtractPublicKey(cert);
	    pubKey = SECU_ConvHighToLow(pubHighKey);
	}
	if (pubKey == NULL) {
	    fprintf(stderr, "Can't extract public key from certificate");
	    exit(1);
	}
	fn = (RSAOp)RSA_PublicKeyOp;
	rsaKey = (void *)(&pubKey->u.rsa);
    }

    if (doPriv) {

	if (!strcmp(nickname, "none")) {
	    privKey = getDefaultRSAPrivateKey();
	} else {
	    cert = CERT_FindCertByNickname(certdb, nickname);
	    if (cert == NULL) {
		fprintf(stderr,
			"Can't find certificate by name \"%s\"\n", nickname);
		exit(1);
	    }

	    privKey = SECKEY_FindKeyByCert(keydb, cert, SECU_GetPassword, NULL);
	}
	if (privKey == NULL) {
	    fprintf(stderr,
		    "Can't find private key by name \"%s\"\n", nickname);
	    exit(1);
	}

	fn = (RSAOp)RSA_PrivateKeyOp;
	rsaKey = (void *)(&privKey->u.rsa);
    }
#else
    if (doPub) {
	pubKey = getDefaultRSAPublicKey();
	fn = (RSAOp)RSA_PublicKeyOp;
	rsaKey = (void *)(&pubKey->u.rsa);
    }
    if (doPriv) {
	privKey = getDefaultRSAPrivateKey();
	fn = (RSAOp)RSA_PrivateKeyOp;
	rsaKey = (void *)(&privKey->u.rsa);
    }
#endif

    memset(buf, 1, sizeof buf);
    rv = fn(rsaKey, buf2, buf);
    if (rv != SECSuccess) {
	PRErrorCode errNum;
	const char * errStr = NULL;

	errNum = PR_GetError();
	if (errNum)
	    errStr = SECU_Strerror(errNum);
	else
	    errNum = rv;
	if (!errStr)
	    errStr = "(null)";
	fprintf(stderr, "Error in RSA operation: %d : %s\n", errNum, errStr);
	exit(1);
    }

/*  printf("START\n");	*/

    timeCtx = CreateTimingContext();
    TimingBegin(timeCtx);
    i = iters;
    while (i--) {
	rv = fn(rsaKey, buf2, buf);
	if (rv != SECSuccess) {
	    PRErrorCode errNum = PR_GetError();
	    const char * errStr = SECU_Strerror(errNum);
	    fprintf(stderr, "Error in RSA operation: %d : %s\n", 
		    errNum, errStr);
	    exit(1);
	}
    }
    TimingEnd(timeCtx);
    printf("%ld iterations in %s\n",
	   iters, TimingGenerateString(timeCtx));
    TimingDivide(timeCtx, iters);
    printf("one operation every %s\n", TimingGenerateString(timeCtx));

    return 0;
}
