/*
 * xpkFEAL.library V1.04
 *
 * Copyright (C) 1992,93,94 by Christian von Roques
 *
 */

#include <exec/types.h>
#include <pragma/exec_lib.h>
#include <exec/memory.h>
#include <xpk/xpksub.h>

#ifdef __MAXON__
  #define __asm
#endif

typedef struct { ULONG lft, rgt; } ULARGE;

#define MaxN 64    /* Maximum # of Rounds */
typedef UWORD tPartKeys[MaxN+8];           /* array[0..N+7] OF UWORD      */

UBYTE __asm S0(register __d0 UBYTE, register __d1 UBYTE);
UBYTE __asm S1(register __d0 UBYTE, register __d1 UBYTE);
ULONG fk(ULONG, ULONG);
void MakePartKeys(tPartKeys, ULARGE, LONG);
void PermPartKeys(tPartKeys, LONG);
ULARGE password(STRPTR);

#ifdef __cplusplus
  extern "C" {
#endif
ULONG __asm f_asm(register __d0 ULONG, register __d1 UWORD);
void __asm FEAL_asm(register __a0 ULARGE *, register __a1 ULARGE *,
	register __a2 tPartKeys, register __d0 LONG);
#ifdef __cplusplus
  }
#endif

#ifndef NO_LIBCODE

#define ECB_MODE 0      /* deactivated */
/* ECB: C[i]=FEAL(T[i])  T[i]=IFEAL(C[i]) */

#define CBC1_MODE 1     /* default */
/* CBCj: C[i]=FEAL(T[i]^C[i-j]) T[i]=IFEAL(C[i])^C[i-j] */

/* ... more modes will follow! ... */

// next upto flags packmem unpackmem packspeed unpackspeed ratio reserved
struct XpkMode Mode[] = {
  Mode+1,  4,XPKMF_A3000SPEED,0,0,238,244,0,0,"fastest",
  Mode+2,  8,XPKMF_A3000SPEED,0,0,171,174,0,0,"fast",
  Mode+3, 16,XPKMF_A3000SPEED,0,0,109,109,0,0,"safe",
  Mode+4, 32,XPKMF_A3000SPEED,0,0, 63, 63,0,0,"safer",
  NULL,  100,XPKMF_A3000SPEED,0,0, 34, 34,0,0,"safest"
};

static struct XpkInfo FealInfo = {
        1,                      /* info version */
        1,                      /* lib  version */
        0,                      /* master vers  */
        0,                      /* ModesVersion */
        "FEAL",                 /* short name   */
        "Fast Encryption ALgorithm",
                                /* long name    */
        "FEAL-N with CBC1. Password protects data with selectable safety.",
                                /* description  */
        0x4645414C,		/* 'FEAL', 4 letter ID  */
        XPKIF_PK_CHUNK  |       /* flags        */
        XPKIF_UP_CHUNK  |
        XPKIF_MODES     |
        XPKIF_ENCRYPTION|
        XPKIF_NEEDPASSWD|
        XPKIF_MODES,
        0x40000000, /* 1GB */   /* max in chunk */
        0,                      /* min in chunk */
        32768,                  /* def in chunk */
        "Encrypting",           /* pk message   */
        "Decrypting",           /* up message   */
        "Encrypted",            /* pk past msg  */
        "Decrypted",            /* up past msg  */
        16,                     /* def mode     */
        0,                      /* pad          */
        &Mode[0]                /* modes        */
};

/*
 * Returns the info structure of our encryptor
 */

#ifdef __cplusplus
  extern "C"
#endif

struct XpkInfo * __asm XpksPackerInfo( void )
{
  return &FealInfo;
}

#ifdef __cplusplus
  extern "C"
#endif

void __asm XpksPackFree( register __a0 struct XpkSubParams *xpar)
{
  if(xpar->xsp_Sub[0])
    FreeMem((APTR)xpar->xsp_Sub[0], sizeof(tPartKeys));
}

/* XpksPackReset is defined in ASM Header !!! */

#ifdef __cplusplus
  extern "C"
#endif

void __asm XpksUnpackFree( register __a0 struct XpkSubParams *xpar)
{
  if (xpar->xsp_Sub[0])
    FreeMem((APTR)xpar->xsp_Sub[0], sizeof(tPartKeys));
}

/*
 * Encrypt a chunk
 */

#ifdef __cplusplus
  extern "C"
#endif

LONG __asm XpksPackChunk( register __a0 struct XpkSubParams *xpar)
{
  ULARGE Key = password(xpar->xsp_Password), T, LastC;
  ULONG  *r = (ULONG*)xpar->xsp_InBuf,
         *w = (ULONG*)xpar->xsp_OutBuf;
  LONG   len = xpar->xsp_InLen, N = xpar->xsp_Mode, i;
  UWORD  csum = 0;

  LastC.lft=LastC.rgt=0;

  N &= ~1;
  if (N<=0) N=2;
  if (N>MaxN) N=MaxN;

  if(xpar->xsp_OutBufLen < ((xpar->xsp_InLen+15) & ~7))
    return XPKERR_SMALLBUF;

  if(!xpar->xsp_Sub[0])
  {
    if(!(xpar->xsp_Sub[0] = (LONG) AllocMem(sizeof(tPartKeys), MEMF_PUBLIC)))
      return XPKERR_NOMEM;
    MakePartKeys((UWORD*)xpar->xsp_Sub[0], Key, N);
  }

  /* fill in FEAL-Headerinformation. */
  ((UBYTE*)xpar->xsp_OutBuf)[2] = CBC1_MODE;
  ((UBYTE*)xpar->xsp_OutBuf)[3] = N;
/*((UWORD*)xpar->xsp_OutBuf)[0] = 0; checksum, filled in later */
  w++;  /* skip FEAL-Header */

  while (len >= 8)
  {
    csum += *r;                                 /* update checksum      */
    T.lft = *r++ ^ LastC.lft;
    T.rgt = *r++ ^ LastC.rgt;
    FEAL_asm(&T, (ULARGE*)w, (UWORD*)xpar->xsp_Sub[0], N);
    LastC.lft=*w++; LastC.rgt=*w++;
    len -= 8;
  }

  w[0]=r[0]; w[1]=r[1];				/* prepare the last block */
  for (i=len; i<7; ++i) ((UBYTE*)w)[i]=0;	/* with 0 to 7 data bytes */
  ((UBYTE*)w)[7]=len;				/* and len as last byte   */

  csum += *w;
  T.lft = *w++ ^ LastC.lft;
  T.rgt = *w++ ^ LastC.rgt;
  FEAL_asm(&T, (ULARGE*)(w-2), (UWORD*)xpar->xsp_Sub[0], N);

  ((UWORD*)xpar->xsp_OutBuf)[0] = csum;             /* fill in checksum     */
  xpar->xsp_OutLen = (UBYTE*)w - (UBYTE*)xpar->xsp_OutBuf;

  return XPKERR_OK;
}

/*
 * Decrypt a chunk
 */

#ifdef __cplusplus
  extern "C"
#endif

LONG __asm XpksUnpackChunk( register __a0 struct XpkSubParams *xpar )
{
  ULARGE Key = password(xpar->xsp_Password),
         LastC;
  ULONG  *r = (ULONG*)xpar->xsp_InBuf,
         *w = (ULONG*)xpar->xsp_OutBuf;
  UWORD  csum;
  LONG   len = xpar->xsp_InLen, N;

  LastC.lft=LastC.rgt=0;

  csum = ((UWORD*)xpar->xsp_InBuf)[0];              /* checksum             */
  N    = ((UBYTE*)xpar->xsp_InBuf)[3];              /* Number of rounds     */
  if((((UBYTE*)xpar->xsp_InBuf)[2]!= CBC1_MODE) ||
      (N > MaxN))
    return XPKERR_VERSION;
  r++; len-=4; /* skip FEAL-Header */

  if(xpar->xsp_OutBufLen) /* fixed xpkMaster with !=0 OutBufLen */
    if(xpar->xsp_OutBufLen < len-8) /* 8:Can't know #bytes in last Block */
      return XPKERR_SMALLBUF;

  if(!xpar->xsp_Sub[0])
  {
    if(!(xpar->xsp_Sub[0] = (LONG)AllocMem(sizeof(tPartKeys), MEMF_PUBLIC)))
      return XPKERR_NOMEM;
    MakePartKeys((UWORD*)xpar->xsp_Sub[0], Key, N);
    PermPartKeys((UWORD*)xpar->xsp_Sub[0], N);
  }

  xpar->xsp_OutLen = len-8;	/* Bytes of last block will be added later */

  while(len > 0)
  {
    FEAL_asm((ULARGE*)r, (ULARGE*)w, (UWORD*)xpar->xsp_Sub[0], N);
    csum -= *w++ ^= LastC.lft;
            *w++ ^= LastC.rgt;
    LastC.lft = *r++;
    LastC.rgt = *r++;
    len-=8;
  }

  xpar->xsp_OutLen += ((UBYTE*)w)[-1];

  if(!csum)
    return XPKERR_OK;
  else
    return XPKERR_WRONGPW;
}

#endif

/* This is a direct translation of the hardware description.
 * CvR Jul. 1992
 */

UBYTE __asm S0(register __d0 UBYTE a, register __d1 UBYTE b)
{
  UWORD c;

  c = ((a + b) & 0x00ff) << 2;
  return (UBYTE) ((c & 0xff) | (c >> 8));
}

UBYTE __asm S1(register __d0 UBYTE a, register __d1 UBYTE b)
{
  UWORD c;

  c = ((a + b + 1) & 0x00ff) << 2;
  return (UBYTE) ((c & 0xff) | (c >> 8));
}

/*
 *  This one needn't be fast, since it's used only for generating the
 *  internal keys from the external one.
 */

ULONG fk(ULONG alpha, ULONG beta)
{
  UBYTE a0, a1, a2, a3, b0, b1, b2, b3, f0, f1, f2, f3;

  /* (a0,a1,a2,a3) = alpha */
  a0 = alpha>>24;
  a1 = alpha>>16;
  a2 = alpha>>8;
  a3 = alpha;

  /* (b0,b1,b2,b3) = beta */
  b0 = beta>>24;
  b1 = beta>>16;
  b2 = beta>>8;
  b3 = beta;

  f1 = a1^a0;
  f2 = a2^a3;
  f1 = S1(f1, f2^b0);
  f2 = S0(f2, f1^b1);
  f0 = S0(a0, f1^b2);
  f3 = S1(a3, f2^b3);

  return (ULONG) ((((ULONG) f0)<<24) | (((ULONG) f1)<<16) |
	         (((ULONG) f2)<<8) | ((ULONG) f3));
}

ULARGE password(STRPTR p)
{
  ULARGE x;
  ULONG t;

  x.lft = x.rgt = 0;

  while (p && *p)
  {
    t = x.lft;
    x.lft = x.rgt;
    x.rgt = ((t << 9) | (t >> 23)) + (ULONG) *p;
    p++;
  }
  return x;
}


void MakePartKeys(tPartKeys K, ULARGE Key, LONG n)
{
  ULONG A, B, C, D, *k;
  LONG i;

  k = (ULONG*) K;
  A = Key.lft;
  B = Key.rgt;
  D = 0;

  for (i= n/2 + 4; i>0; i--)
  {
    C = fk(A, B^D);
    D = A;
    A = B;
    B = C;
    *k++ = C;
  }
}

void PermPartKeys(tPartKeys K, LONG n)
{
  LONG i;
  UWORD T;

  for (i=0; i<n/2; i++)
  {
    T        = K[n-1-i];
    K[n-1-i] = K[i];
    K[i]     = T;
  }
  for (i=0; i<4; i++)
  {
    T        = K[n+i];
    K[n+i]   = K[n+4+i];
    K[n+4+i] = T;
  }
}

