/* crypto/bn/bn_knuth.c */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include "cryptlib.h"
#include "bn.h"

/* This is just a test implementation, it has not been modified for
 * speed and it still has memory leaks. */

int BN_mask_bits(BIGNUM *a,int n);

#undef DEBUG
#define MAIN

/* r must be different to a and b
 * Toom-Cook multiplication algorithm, taken from
 * The Art Of Computer Programming, Volume 2, Donald Knuth
 */

#define	CODE1		((BIGNUM *)0x01)
#define	CODE2		((BIGNUM *)0x02)
#define	CODE3		((BIGNUM *)0x03)
#define MAXK		(30+1)

#define C3	3
#define C4	4
#define C5	5
#define C6	6
#define C7	7
#define C8	8
#define C9	9
#define C10	10
#define DONE	11

int new_total=0;
int Free_total=0;
int max=0,max_total=0;

BIGNUM *LBN_new(void );
BIGNUM *LBN_dup(BIGNUM *a);
void LBN_free(BIGNUM *a);

int BN_mul_knuth(w, a, b)
BIGNUM *w;
BIGNUM *a;
BIGNUM *b;
	{
	int ret=1;
	int i,j,n,an,bn,y,z;
	BIGNUM *U[MAXK],*V[MAXK],*T[MAXK];
	BIGNUM *C[(MAXK*2*3)];
	BIGNUM *W[(MAXK*2)],*t1,*t2,*t3,*t4;
	int Utos,Vtos,Ctos,Wtos,Ttos;
	unsigned int k,Q,R;
	unsigned int q[MAXK];
	unsigned int r[MAXK];
	int state;

	/* C1 */
	Utos=Vtos=Ctos=Wtos=Ttos=0;
	k=1;
	q[0]=q[1]=64;
	r[0]=r[1]=4;
	Q=6;
	R=2;

	if (!bn_expand(w,BN_BITS2*2)) goto err;
	an=BN_num_bits(a);
	bn=BN_num_bits(b);
	n=(an > bn)?an:bn;
	while ((q[k-1]+q[k]) < n)
		{
		k++;
		Q+=R;
		i=R+1;
		if ((i*i) <= Q) R=i;
		q[k]=(1<<Q);
		r[k]=(1<<R);
		}
#ifdef DEBUG
	printf("k   =");
	for (i=0; i<=k; i++) printf("%7d",i);
	printf("\nq[k]=");
	for (i=0; i<=k; i++) printf("%7d",q[i]);
	printf("\nr[k]=");
	for (i=0; i<=k; i++) printf("%7d",r[i]);
	printf("\n");
#endif

	/* C2 */
	C[Ctos++]=CODE1;
	if ((t1=LBN_dup(a)) == NULL) goto err;
	C[Ctos++]=t1;
	if ((t1=LBN_dup(b)) == NULL) goto err;
	C[Ctos++]=t1;

	state=C3;
	for (;;)
		{
#ifdef DEBUG
		printf("state=C%d, Ctos=%d Wtos=%d\n",state,Ctos,Wtos);
#endif
		switch (state)
			{
			int lr,lq,lp;
		case C3:
			k--;
			if (k == 0)
				{
				t1=C[--Ctos];
				t2=C[--Ctos];
#ifdef DEBUG
				printf("Ctos=%d poped %d\n",Ctos,2);
#endif
				if ((t2->top == 0) || (t1->top == 0))
					w->top=0;
				else
					BN_mul(w,t1,t2);

				LBN_free(t1); /* FREE */
				LBN_free(t2); /* FREE */
				state=C10;
				}
			else
				{
				lr=r[k];
				lq=q[k];
				lp=q[k-1]+q[k];
				state=C4;
				}
			break;
		case C4:
			for (z=0; z<2; z++) /* do for u and v */
				{
				/* break the item at C[Ctos-1] 
				 * into lr+1 parts of lq bits each
				 * for j=0; j<=2r; j++
				 */
				t1=C[--Ctos]; /* pop off u */
#ifdef DEBUG
				printf("Ctos=%d poped %d\n",Ctos,1);
#endif
				if ((t2=LBN_dup(t1)) == NULL) goto err;
				BN_mask_bits(t2,lq);
				T[Ttos++]=t2;
#ifdef DEBUG
				printf("C4 r=0 bits=%d\n",BN_num_bits(t2));
#endif
				for (i=1; i<=lr; i++)
					{
					if (!BN_rshift(t1,t1,lq)) goto err;
					if ((t2=LBN_dup(t1)) == NULL) goto err;
					BN_mask_bits(t2,lq);
					T[Ttos++]=t2;
#ifdef DEBUG
					printf("C4 r=%d bits=%d\n",i,
						BN_num_bits(t2));
#endif
					}
				LBN_free(t1);

				if ((t2=LBN_new()) == NULL) goto err;
				if ((t3=LBN_new()) == NULL) goto err;
				for (j=0; j<=2*lr; j++)
					{
					if ((t1=LBN_new()) == NULL) goto err;

					if (!BN_set_word(t3,j)) goto err;
					for (i=lr; i>=0; i--)
						{
						if (!BN_mul(t2,t1,t3)) goto err;
						if (!BN_add(t1,t2,T[i])) goto err;
						}
					/* t1 is U(j) */
					if (z == 0)
						U[Utos++]=t1;
					else
						V[Vtos++]=t1;
					}
				LBN_free(t2);
				LBN_free(t3);
				while (Ttos) LBN_free(T[--Ttos]);
				}
#ifdef DEBUG
			for (i=0; i<Utos; i++)
				printf("U[%2d]=%4d bits\n",i,BN_num_bits(U[i]));
			for (i=0; i<Vtos; i++)
				printf("V[%2d]=%4d bits\n",i,BN_num_bits(V[i]));
#endif
			/* C5 */
#ifdef DEBUG
			printf("PUSH CODE2 and %d CODE3 onto stack\n",2*lr);
#endif
			C[Ctos++]=CODE2;
			for (i=2*lr; i>0; i--)
				{
				C[Ctos++]=V[i];
				C[Ctos++]=U[i];
				C[Ctos++]=CODE3;
				}
			C[Ctos++]=V[0];
			C[Ctos++]=U[0];
#ifdef DEBUG
				printf("Ctos=%d pushed %d\n",Ctos,2*lr*3+3);
#endif
			Vtos=Utos=0;
			state=C3;
			break;
		case C6:
			if ((t1=LBN_dup(w)) == NULL) goto err;
			W[Wtos++]=t1;
#ifdef DEBUG
			printf("put %d bit number onto w\n",BN_num_bits(t1));
#endif
			state=C3;
			break;
		case C7:
			lr=r[k];
			lq=q[k];
			lp=q[k]+q[k-1];
			z=Wtos-2*lr-1;
			for (j=1; j<=2*lr; j++)
				{
				for (i=2*lr; i>=j; i--)
					{
					if (!BN_sub(W[z+i],W[z+i],W[z+i-1])) goto err;
					BN_div_word(W[z+i],j);
					}
				}
			state=C8;
			break;
		case C8:
			y=2*lr-1;
			if ((t1=LBN_new()) == NULL) goto err;
			if ((t3=LBN_new()) == NULL) goto err;

			for (j=y; j>0; j--)
				{
				if (!BN_set_word(t3,j)) goto err;
				for (i=j; i<=y; i++)
					{
					if (!BN_mul(t1,W[z+i+1],t3)) goto err;
					if (!BN_sub(W[z+i],W[z+i],t1)) goto err;
					}
				}
			LBN_free(t1);
			LBN_free(t3);
			state=C9;
			break;
		case C9:
			BN_zero(w);
#ifdef DEBUG
			printf("lq=%d\n",lq);
#endif
			for (i=lr*2; i>=0; i--)
				{
				BN_lshift(w,w,lq);
				BN_add(w,w,W[z+i]);
				}
			for (i=0; i<=lr*2; i++)
				LBN_free(W[--Wtos]);
			state=C10;
			break;
		case C10:
			k++;
			t1=C[--Ctos];
#ifdef DEBUG
			printf("Ctos=%d poped %d\n",Ctos,1);
			printf("code= CODE%d\n",t1);
#endif
			if (t1 == CODE3)
				state=C6;
			else if (t1 == CODE2)
				{
				if ((t2=LBN_dup(w)) == NULL) goto err;
				W[Wtos++]=t2;
				state=C7;
				}
			else if (t1 == CODE1)
				{
				state=DONE;
				}
			else
				{
				printf("BAD ERROR\n");
				goto err;
				}
			break;
		default:
			printf("bad state\n");
			goto err;
			break;
			}
		if (state == DONE) break;
		}
	ret=1;
err:
	if (ret == 0) printf("ERROR\n");
	return(ret);
	}

#ifdef MAIN
main()
	{
	BIGNUM *a,*b,*r;
	int i;

	if ((a=LBN_new()) == NULL) goto err;
	if ((b=LBN_new()) == NULL) goto err;
	if ((r=LBN_new()) == NULL) goto err;

	if (!BN_rand(a,1024*2,0,0)) goto err;
	if (!BN_rand(b,1024*2,0,0)) goto err;

	for (i=0; i<10; i++)
		{
		if (!BN_mul_knuth(r,a,b)) goto err; /**/
		/*if (!BN_mul(r,a,b)) goto err; /**/
		}
BN_print(stdout,a); printf(" * ");
BN_print(stdout,b); printf(" =\n");
BN_print(stdout,r); printf("\n");

printf("BN_new() =%d\nBN_free()=%d max=%d\n",new_total,Free_total,max);


	exit(0);
err:
	ERR_load_crypto_strings();
	ERR_print_errors(stderr);
	exit(1);
	}
#endif

int BN_mask_bits(a,n)
BIGNUM *a;
int n;
	{
	int b,w;

	w=n/BN_BITS2;
	b=n%BN_BITS2;
	if (w >= a->top) return(0);
	if (b == 0)
		a->top=w;
	else
		{
		a->top=w+1;
		a->d[w]&= ~(BN_MASK2<<b);
		}
	return(1);
	}

BIGNUM *LBN_dup(a)
BIGNUM *a;
	{
	new_total++;
	max_total++;
	if (max_total > max) max=max_total;
	return(BN_dup(a));
	}

BIGNUM *LBN_new()
	{
	new_total++;
	max_total++;
	if (max_total > max) max=max_total;
	return(BN_new());
	}

void LBN_free(a)
BIGNUM *a;
	{
	max_total--;
	if (max_total > max) max=max_total;
	Free_total++;
	BN_free(a);
	}
