/*
 * crypto - Tcl bindings for parts of the `nettle' crypto library
 * Copyright 2006-2012 Ian Jackson
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, see <http://www.gnu.org/licenses/>.
 */

  
#include "chiark_tcl_crypto.h"

static const char *mode_cbc_encrypt(Byte *data, int blocks,
				    const Byte *iv, Byte *chain,
				    const BlockCipherAlgInfo *alg, int encr,
				    const void *sch) {
  int blocksize= alg->blocksize;
  memcpy(chain,iv,blocksize);
  
  while (blocks > 0) {
    memxor(data, chain, blocksize);
    alg->encrypt.crypt(sch, data, data);
    memcpy(chain, data, blocksize);

    blocks--; data += blocksize;
  }
  return 0;
}

static const char *mode_cbc_decrypt(Byte *data, int blocks,
				    const Byte *iv, Byte *chain,
				    const BlockCipherAlgInfo *alg, int encr,
				    const void *sch) {
  int blocksize= alg->blocksize;
  int cchain= 0;

  memcpy(chain,iv,blocksize);
  
  while (blocks > 0) {
    memcpy(chain + (cchain^blocksize), data, blocksize);
    alg->decrypt.crypt(sch, data, data);
    memxor(data, chain + cchain, blocksize);
    cchain ^= blocksize;

    blocks--; data += blocksize;
  }
  return 0;
}

static void cbcmac_core(const Byte *data, int blocks,
			const Byte *iv, Byte *buf,
			const BlockCipherAlgInfo *alg,
			const void *sch) {
  int blocksize= alg->blocksize;

  memcpy(buf,iv,blocksize);
  
  while (blocks > 0) {
    memcpy(buf + blocksize, data, blocksize);
    memxor(buf, buf + blocksize, blocksize);
    
    alg->encrypt.crypt(sch, buf, buf);

    blocks--; data += blocksize;
  }
}  

static const char *mode_cbc_mac(const Byte *data, int blocks,
				const Byte *iv, Byte *buf,
				const BlockCipherAlgInfo *alg,
				const void *sch) {
  cbcmac_core(data,blocks,iv,buf,alg,sch);
  return 0;
}

static const char *mode_cbc_mac2(const Byte *data, int blocks,
				 const Byte *iv, Byte *buf,
				 const BlockCipherAlgInfo *alg,
				 const void *sch) {
  cbcmac_core(data,blocks,iv,buf,alg,sch);
  alg->encrypt.crypt(sch, buf, buf);
  return 0;
}

static const char *mode_ecb(Byte *data, int blocks,
			    const Byte *iv, Byte *chain,
			    const BlockCipherAlgInfo *alg, int encr,
			    const void *sch) {
  int blocksize= alg->blocksize;
  
  while (blocks > 0) {
    (encr ? &alg->encrypt : &alg->decrypt)->crypt(sch, data, data);
    blocks--; data += blocksize;
  }
  return 0;
}

static const char *mode_ctr(Byte *data, int blocks,
			    const Byte *iv, Byte *counter,
			    const BlockCipherAlgInfo *alg, int encr,
			    const void *sch) {
  int blocksize= alg->blocksize;
  Byte *cipher= counter + blocksize;
  int byte;

  memcpy(counter, iv, blocksize);
  while (blocks > 0) {
    alg->encrypt.crypt(sch, counter, cipher);
    memxor(data, cipher, blocksize);
    for (byte=blocksize-1; byte>=0; byte--) {
      if (++counter[byte]) break;
      /* new value of zero implies carry, so increment next byte */
    }
    blocks--;
    data += blocksize;
  }
  return 0;
}

const BlockCipherModeInfo cht_blockciphermodeinfo_entries[]= {
  { "cbc",      1, 2, 1, mode_cbc_encrypt, mode_cbc_decrypt, mode_cbc_mac  },
  { "cbc-mac2", 1, 2, 1, 0,                0,                mode_cbc_mac2 },
  { "ecb",      0, 0, 0, mode_ecb,         mode_ecb,         0             },
  { "ctr-sif",  1, 2, 0, mode_ctr,         mode_ctr,         0             },
  { 0 }
};
