#include "libopgp.h"

#include <idea.h>
#include <des.h>
#include <cast.h>
#include <blowfish.h>
#ifndef NO_SAFER
#include "safer.h"
#endif

union cipkey {
  IDEA_KEY_SCHEDULE iks;
  des_key_schedule dk[3];
  CAST_KEY castkey;
  BF_KEY bfkey;
#ifndef NO_SAFER
  safer_key_t sfkey;
#endif
};

#define MAXBSZ 8
struct cipctx {
  u8_t cipnum;                  /* algorithm number */
  u8_t ed;                      /* encrypt if nonzero else decrypt */
  u32_t bsiz;                   /* block size, all are 8 */
  unsigned int irm;             /* remaining bytes in feedback */
  u8_t civ[MAXBSZ];             /* feedback register */
  union cipkey ck;              /* formatted key material */
};

static const int glbcipsiz[] =
{0, 16, 24, 16, 16, 16};
int PGP_cksz(int cipher)
{
  if (cipher > 0 && cipher < 6)
    return glbcipsiz[cipher];
  return -1;
}

void *PGP_cini(u8_t * key, u8_t * iv0, u8_t cipno, u8_t enc)
{
  struct cipctx *ctx;

  ctx = malloc(sizeof(struct cipctx));
  ctx->cipnum = cipno;
  ctx->irm = 0;
  ctx->bsiz = 8;
  memcpy(ctx->civ, iv0, ctx->bsiz);
  ctx->ed = enc;
  switch (cipno) {
  case 1:
    idea_set_encrypt_key(key, &ctx->ck.iks);
    ctx->ed = enc ? IDEA_ENCRYPT : IDEA_DECRYPT;
    break;
  case 2:
    des_set_key((des_cblock *) key, ctx->ck.dk[0]);
    des_set_key((des_cblock *) & key[8], ctx->ck.dk[1]);
    des_set_key((des_cblock *) & key[16], ctx->ck.dk[2]);
    ctx->ed = enc ? DES_ENCRYPT : DES_DECRYPT;
    break;
  case 3:
    CAST_set_key(&ctx->ck.castkey, CAST_KEY_LENGTH, key);
    ctx->ed = enc ? CAST_ENCRYPT : CAST_DECRYPT;
    break;
  case 4:
    BF_set_key(&ctx->ck.bfkey, 16, key);
    ctx->ed = enc ? BF_ENCRYPT : BF_DECRYPT;
    break;
#ifndef NO_SAFER
  case 5:
    Safer_Init_Module();
    Safer_Expand_Userkey(key, &key[8], SAFER_MAX_NOF_ROUNDS, 1, ctx->ck.sfkey);
    break;
#endif
  default:
    exit(-1);
  }
  return ctx;
}

static void cfbomatic(u8_t * buf, u32_t len, struct cipctx *cctx)
{
  int i, ilen;
  u8_t liv;

  while (len) {
    ilen = len > cctx->bsiz ? cctx->bsiz : len;
    if (cctx->irm == 0)
      switch (cctx->cipnum) {
#ifndef NO_SAFER
      case 5:
        Safer_Encrypt_Block(cctx->civ, cctx->ck.sfkey, cctx->civ);
        break;
#endif
    } else if (ilen + cctx->irm > cctx->bsiz)
      ilen = cctx->bsiz - cctx->irm;
    for (i = cctx->irm; i < ilen + cctx->irm; i++) {
      liv = *buf;
      *buf++ ^= cctx->civ[i];
      if (cctx->ed)
        liv ^= cctx->civ[i];
      cctx->civ[i] = liv;
    }
    len -= ilen;
    cctx->irm = (cctx->irm + ilen) & 7;
  }
}

void PGP_cblk(u8_t * buf, u32_t ln, void *cctx)
{
  struct cipctx *C = cctx;

  if (C->cipnum == 2)
    des_ede3_cfb64_encrypt(buf, buf, ln, C->ck.dk[0], C->ck.dk[1], C->ck.dk[2],
                           (des_cblock *) C->civ, &C->irm, C->ed);
  else if (C->cipnum == 3)
    CAST_cfb64_encrypt(buf, buf, ln, &C->ck.castkey, C->civ, &C->irm, C->ed);
  else if (C->cipnum == 1)
    idea_cfb64_encrypt(buf, buf, ln, &C->ck.iks, C->civ, &C->irm, C->ed);
  else if (C->cipnum == 4)
    BF_cfb64_encrypt(buf, buf, ln, &C->ck.bfkey, C->civ, &C->irm, C->ed);
  else
    cfbomatic(buf, ln, C);
}

void PGP_cres(u8_t * save, void *cctx)
{
  struct cipctx *CCTX = cctx;

  if (CCTX->bsiz > 9)
    return;
  memcpy((struct cipctx *) CCTX->civ, save, CCTX->bsiz);
  CCTX->irm = 0;
}
