/* sml3_cksum.c: Funktionen fuer Checksummen-Hashs */

/* Copyright 2012-2017 Kurt Nienhaus
 *
 * This file is part of libsammel3.
 * libsammel3 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.
 * libsammel3 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 libsammel3.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include "config.h"
#include "sml3_fehler.h"
#include "sml3_util.h"
#include "sml3_cksum.h"
#include "cksum/cksum.h"

void * SML3_cksum_init(int);
void * SML3_cksum_hmac_init(int, const char *, size_t);
void SML3_cksum_add(void *, const void *, size_t);
char * SML3_cksum_result(void *, char *, size_t);

struct s_horner {
  unsigned SML3_int64 vl;
  int anzahl;
  unsigned int primz;
};

static void horner_init(struct s_horner *, int);
static void horner_add(struct s_horner *, const void *, size_t);
static void horner_result(struct s_horner *, char *);

struct cks_intern {
  int digest, hmac;
  void *ptr;
};


/* Horner-Pruefsumme: Initialisierung */
static void
horner_init(struct s_horner *cks, int ckdigest)
{
  if (cks == NULL) { return; }

  cks->vl = 0;
  if (ckdigest == SML3_CKSUM_DIGEST_HORNER2) {
    cks->anzahl = 2;
    cks->primz = 97;
  } else if (ckdigest == SML3_CKSUM_DIGEST_HORNER3) {
    cks->anzahl = 3;
    cks->primz = 997;
  } else if (ckdigest == SML3_CKSUM_DIGEST_HORNER4) {
    cks->anzahl = 4;
    cks->primz = 9973;
  } else if (ckdigest == SML3_CKSUM_DIGEST_HORNER5) {
    cks->anzahl = 5;
    cks->primz = 99991;
  } else if (ckdigest == SML3_CKSUM_DIGEST_HORNER6) {
    cks->anzahl = 6;
    cks->primz = 999983;
  } else if (ckdigest == SML3_CKSUM_DIGEST_HORNER7) {
    cks->anzahl = 7;
    cks->primz = 9999991;
  } else if (ckdigest == SML3_CKSUM_DIGEST_HORNER8) {
    cks->anzahl = 8;
    cks->primz = 99999989;
  } else if (ckdigest == SML3_CKSUM_DIGEST_HORNER9) {
    cks->anzahl = 9;
    cks->primz = 999999937;
  } else {
    cks->anzahl = 0;
    cks->primz = 0;
  }
} /* Ende horner_init */


/* Horner-Pruefsumme: Daten hinzufuegen */
static void
horner_add(struct s_horner *cks, const void *daten, size_t dsize)
{
  const char *cptr;
  size_t dpos;

  if (cks == NULL || cks->anzahl == 0) { return; }
  if (daten == NULL || dsize == 0) { return; }

  cptr = (const char *)daten;
  for (dpos = 0; dpos < dsize; dpos++) {
    cks->vl = (cks->vl * 256 + (const unsigned char)cptr[dpos]) % cks->primz;  /* Horner-Schema */
  }
} /* Ende horner_add */


/* Horner-Pruefsumme: Ergebnis zurueckgeben */
static void
horner_result(struct s_horner *cks, char *binres)
{
  char fmt[16];

  if (binres != NULL) { strcpy(binres, "0"); }
  if (cks == NULL || cks->anzahl == 0 || binres == NULL) { return; }

  snprintf(fmt, sizeof(fmt), "%%0%du", cks->anzahl);
  sprintf(binres, fmt, (unsigned int)cks->vl);
} /* Ende horner_result */


/* SML3_cksum_init [thread-sicher]:
 * Checksummen-Berechnung: Initialisierung
 * 1.Arg: Checksummen-Digest (SML3_CKSUM_DIGEST_*)
 * Rueckgabe: Checksummen-Struct
 */
void *
SML3_cksum_init(int ckdigest)
{
  struct cks_intern *cksi;

  cksi = SML3_calloc(1, sizeof(*cksi));
  cksi->digest = ckdigest;
  cksi->hmac = 0;

  if (ckdigest == SML3_CKSUM_DIGEST_HORNER2
    || ckdigest == SML3_CKSUM_DIGEST_HORNER3
    || ckdigest == SML3_CKSUM_DIGEST_HORNER4
    || ckdigest == SML3_CKSUM_DIGEST_HORNER5
    || ckdigest == SML3_CKSUM_DIGEST_HORNER6
    || ckdigest == SML3_CKSUM_DIGEST_HORNER7
    || ckdigest == SML3_CKSUM_DIGEST_HORNER8
    || ckdigest == SML3_CKSUM_DIGEST_HORNER9) {  /* Horner-Pruefsumme */
    struct s_horner *cks;
    cks = SML3_calloc(1, sizeof(*cks));
    horner_init(cks, ckdigest);
    cksi->ptr = cks;

  } else if (ckdigest == SML3_CKSUM_DIGEST_MD5) {  /* MD5 */
    MD5_CTX *cks;
    cks = SML3_calloc(1, sizeof(*cks));
    cksum_MD5_Init(cks);
    cksi->ptr = cks;

  } else if (ckdigest == SML3_CKSUM_DIGEST_SHA1) {  /* SHA-1 */
    sha1nfo *cks;
    cks = SML3_calloc(1, sizeof(*cks));
    cksum_sha1_init(cks);
    cksi->ptr = cks;

  } else if (ckdigest == SML3_CKSUM_DIGEST_SHA2_224) {  /* SHA-2-224 */
    sha224_ctx *cks;
    cks = SML3_calloc(1, sizeof(*cks));
    cksum_sha224_init(cks);
    cksi->ptr = cks;

  } else if (ckdigest == SML3_CKSUM_DIGEST_SHA2_256) {  /* SHA-2-256 */
    sha256_ctx *cks;
    cks = SML3_calloc(1, sizeof(*cks));
    cksum_sha256_init(cks);
    cksi->ptr = cks;

  } else if (ckdigest == SML3_CKSUM_DIGEST_SHA2_384) {  /* SHA-2-384 */
    sha384_ctx *cks;
    cks = SML3_calloc(1, sizeof(*cks));
    cksum_sha384_init(cks);
    cksi->ptr = cks;

  } else if (ckdigest == SML3_CKSUM_DIGEST_SHA2_512) {  /* SHA-2-512 */
    sha512_ctx *cks;
    cks = SML3_calloc(1, sizeof(*cks));
    cksum_sha512_init(cks);
    cksi->ptr = cks;

  } else {
    free(cksi);
    SML3_fehlerexit("%s", SML3_strerror(EINVAL));
  }

  return cksi;
} /* Ende SML3_cksum_init */


/* SML3_cksum_hmac_init [thread-sicher]:
 * HMAC-Checksummen-Berechnung: Initialisierung
 * 1.Arg: Checksummen-Digest (SML3_CKSUM_DIGEST_*) ohne: SML3_CKSUM_DIGEST_MD5, SML3_CKSUM_DIGEST_HORNER*
 * Rueckgabe: Checksummen-Struct
 *
 * Weitere Operationen finden mit SML3_cksum_add() und SML3_cksum_result() statt
 */
void *
SML3_cksum_hmac_init(int ckdigest, const char *key, size_t keylen)
{
  struct cks_intern *cksi;

  if (key == NULL || keylen == 0) { SML3_fehlerexit("%s", SML3_strerror(EINVAL)); }

  cksi = SML3_calloc(1, sizeof(*cksi));
  cksi->digest = ckdigest;
  cksi->hmac = 1;

  if (ckdigest == SML3_CKSUM_DIGEST_HORNER2
    || ckdigest == SML3_CKSUM_DIGEST_HORNER3
    || ckdigest == SML3_CKSUM_DIGEST_HORNER4
    || ckdigest == SML3_CKSUM_DIGEST_HORNER5
    || ckdigest == SML3_CKSUM_DIGEST_HORNER6
    || ckdigest == SML3_CKSUM_DIGEST_HORNER7
    || ckdigest == SML3_CKSUM_DIGEST_HORNER8
    || ckdigest == SML3_CKSUM_DIGEST_HORNER9) {  /* Horner-Pruefsumme */
    free(cksi);
    SML3_fehlerexit("HMAC-Horner: %s", SML3_strerror(EAFNOSUPPORT));

  } else if (ckdigest == SML3_CKSUM_DIGEST_MD5) {  /* HMAC-MD5 */
    free(cksi);
    SML3_fehlerexit("HMAC-MD5: %s", SML3_strerror(EAFNOSUPPORT));

  } else if (ckdigest == SML3_CKSUM_DIGEST_SHA1) {  /* HMAC-SHA-1 */
    sha1nfo *cks;
    cks = SML3_calloc(1, sizeof(*cks));
    cksum_sha1_initHmac(cks, (const uint8_t *)key, keylen);
    cksi->ptr = cks;

  } else if (ckdigest == SML3_CKSUM_DIGEST_SHA2_224) {  /* SHA-2-224 */
    hmac_sha224_ctx *cks;
    cks = SML3_calloc(1, sizeof(*cks));
    cksum_hmac_sha224_init(cks, (const unsigned char *)key, keylen);
    cksi->ptr = cks;

  } else if (ckdigest == SML3_CKSUM_DIGEST_SHA2_256) {  /* SHA-2-256 */
    hmac_sha256_ctx *cks;
    cks = SML3_calloc(1, sizeof(*cks));
    cksum_hmac_sha256_init(cks, (const unsigned char *)key, keylen);
    cksi->ptr = cks;

  } else if (ckdigest == SML3_CKSUM_DIGEST_SHA2_384) {  /* SHA-2-384 */
    hmac_sha384_ctx *cks;
    cks = SML3_calloc(1, sizeof(*cks));
    cksum_hmac_sha384_init(cks, (const unsigned char *)key, keylen);
    cksi->ptr = cks;

  } else if (ckdigest == SML3_CKSUM_DIGEST_SHA2_512) {  /* SHA-2-512 */
    hmac_sha512_ctx *cks;
    cks = SML3_calloc(1, sizeof(*cks));
    cksum_hmac_sha512_init(cks, (const unsigned char *)key, keylen);
    cksi->ptr = cks;

  } else {
    free(cksi);
    SML3_fehlerexit("%s", SML3_strerror(EINVAL));
  }

  return cksi;
} /* Ende SML3_cksum_hmac_init */


/* SML3_cksum_add [thread-sicher]:
 * Checksummen-Berechnung: Daten hinzufuegen
 * 1.Arg: Checksummen-Struct
 * 2.Arg: hinzuzufuegende Daten
 * 3.Arg: Anzahl Bytes im 2.Arg
 */
void
SML3_cksum_add(void *vks, const void *daten, size_t dsize)
{
  struct cks_intern *cksi = (struct cks_intern *)vks;

  if (cksi == NULL || cksi->ptr == NULL || daten == NULL || dsize == 0) { return; }

  if (cksi->digest == SML3_CKSUM_DIGEST_HORNER2
    || cksi->digest == SML3_CKSUM_DIGEST_HORNER3
    || cksi->digest == SML3_CKSUM_DIGEST_HORNER4
    || cksi->digest == SML3_CKSUM_DIGEST_HORNER5
    || cksi->digest == SML3_CKSUM_DIGEST_HORNER6
    || cksi->digest == SML3_CKSUM_DIGEST_HORNER7
    || cksi->digest == SML3_CKSUM_DIGEST_HORNER8
    || cksi->digest == SML3_CKSUM_DIGEST_HORNER9) {  /* Horner-Pruefsumme */
    struct s_horner *cks = (struct s_horner *)cksi->ptr;
    horner_add(cks, daten, dsize);

  } else if (cksi->digest == SML3_CKSUM_DIGEST_MD5) {  /* MD5 */
    MD5_CTX *cks = (MD5_CTX *)cksi->ptr;
    cksum_MD5_Update(cks, daten, dsize);

  } else if (cksi->digest == SML3_CKSUM_DIGEST_SHA1) {  /* SHA-1 */
    sha1nfo *cks = (sha1nfo *)cksi->ptr;
    cksum_sha1_write(cks, daten, dsize);

  } else if (cksi->digest == SML3_CKSUM_DIGEST_SHA2_224) {  /* SHA-2-224 */
    if (cksi->hmac) {
      hmac_sha224_ctx *cks = (hmac_sha224_ctx *)cksi->ptr;
      cksum_hmac_sha224_update(cks, (const unsigned char *)daten, dsize);
    } else {
      sha224_ctx *cks = (sha224_ctx *)cksi->ptr;
      cksum_sha224_update(cks, (const unsigned char *)daten, dsize);
    }

  } else if (cksi->digest == SML3_CKSUM_DIGEST_SHA2_256) {  /* SHA-2-256 */
    if (cksi->hmac) {
      hmac_sha256_ctx *cks = (hmac_sha256_ctx *)cksi->ptr;
      cksum_hmac_sha256_update(cks, (const unsigned char *)daten, dsize);
    } else {
      sha256_ctx *cks = (sha256_ctx *)cksi->ptr;
      cksum_sha256_update(cks, (const unsigned char *)daten, dsize);
    }

  } else if (cksi->digest == SML3_CKSUM_DIGEST_SHA2_384) {  /* SHA-2-384 */
    if (cksi->hmac) {
      hmac_sha384_ctx *cks = (hmac_sha384_ctx *)cksi->ptr;
      cksum_hmac_sha384_update(cks, (const unsigned char *)daten, dsize);
    } else {
      sha384_ctx *cks = (sha384_ctx *)cksi->ptr;
      cksum_sha384_update(cks, (const unsigned char *)daten, dsize);
    }

  } else if (cksi->digest == SML3_CKSUM_DIGEST_SHA2_512) {  /* SHA-2-512 */
    if (cksi->hmac) {
      hmac_sha512_ctx *cks = (hmac_sha512_ctx *)cksi->ptr;
      cksum_hmac_sha512_update(cks, (const unsigned char *)daten, dsize);
    } else {
      sha512_ctx *cks = (sha512_ctx *)cksi->ptr;
      cksum_sha512_update(cks, (const unsigned char *)daten, dsize);
    }

  } else {
    return;
  }
} /* Ende SML3_cksum_add */


/* SML3_cksum_result [thread-sicher]:
 * Checksummen-Berechnung: Ergebnis erhalten,
 *                         1.Arg wird freigegeben,
 *                         braucht SML3_cksum_init() vor weiterer Verwendung
 * 1.Arg: Checksummen-Struct
 * 2.+3.Arg: fuer Rueckgabe Ergebnis
 * Rueckgabe: Pointer auf 2.Arg
 */
char *
SML3_cksum_result(void *vks, char *result, size_t rsize)
{
  struct cks_intern *cksi = (struct cks_intern *)vks;
  int i1, imax;

  if (result != NULL && rsize > 0) { memset(result, 0, rsize); }

  if (cksi == NULL || cksi->ptr == NULL || result == NULL || rsize == 0) {
    if (cksi != NULL) {
      if (cksi->ptr != NULL) { free(cksi->ptr); }
      free(cksi);
    }
    return result;
  }

  if (cksi->digest == SML3_CKSUM_DIGEST_HORNER2
    || cksi->digest == SML3_CKSUM_DIGEST_HORNER3
    || cksi->digest == SML3_CKSUM_DIGEST_HORNER4
    || cksi->digest == SML3_CKSUM_DIGEST_HORNER5
    || cksi->digest == SML3_CKSUM_DIGEST_HORNER6
    || cksi->digest == SML3_CKSUM_DIGEST_HORNER7
    || cksi->digest == SML3_CKSUM_DIGEST_HORNER8
    || cksi->digest == SML3_CKSUM_DIGEST_HORNER9) {  /* Horner-Pruefsumme */
    char binres[16];
    struct s_horner *cks = (struct s_horner *)cksi->ptr;

    horner_result(cks, binres);
    snprintf(result, rsize, "%s", binres);

  } else if (cksi->digest == SML3_CKSUM_DIGEST_MD5) {  /* MD5 */
    unsigned char binres[16];
    MD5_CTX *cks = (MD5_CTX *)cksi->ptr;

    cksum_MD5_Final(binres, cks);
    imax = ((int)rsize  - 1) / 2; 
    if (imax > 16) { imax = 16; }
    for (i1 = 0; i1 < imax; i1++) { sprintf(result + i1 * 2, "%02x", (unsigned int)binres[i1]); }

  } else if (cksi->digest == SML3_CKSUM_DIGEST_SHA1) {  /* SHA-1 */
    sha1nfo *cks = (sha1nfo *)cksi->ptr;
    uint8_t *binres;

    if (cksi->hmac) {
      binres = cksum_sha1_resultHmac(cks);
    } else {
      binres = cksum_sha1_result(cks);
    }
    imax = ((int)rsize  - 1) / 2; 
    if (imax > 20) { imax = 20; }
    for (i1 = 0; i1 < imax; i1++) { sprintf(result + i1 * 2, "%02x", (unsigned int)binres[i1]); }

  } else if (cksi->digest == SML3_CKSUM_DIGEST_SHA2_224) {  /* SHA-2-224 */
    unsigned char binres[SHA224_DIGEST_SIZE];
    if (cksi->hmac) {
      hmac_sha224_ctx *cks = (hmac_sha224_ctx *)cksi->ptr;
      cksum_hmac_sha224_final(cks, binres, sizeof(binres));
    } else {
      sha224_ctx *cks = (sha224_ctx *)cksi->ptr;
      cksum_sha224_final(cks, binres);
    }
    imax = ((int)rsize  - 1) / 2; 
    if (imax > SHA224_DIGEST_SIZE) { imax = SHA224_DIGEST_SIZE; }
    for (i1 = 0; i1 < imax; i1++) { sprintf(result + i1 * 2, "%02x", (unsigned int)binres[i1]); }

  } else if (cksi->digest == SML3_CKSUM_DIGEST_SHA2_256) {  /* SHA-2-256 */
    unsigned char binres[SHA256_DIGEST_SIZE];
    if (cksi->hmac) {
      hmac_sha256_ctx *cks = (hmac_sha256_ctx *)cksi->ptr;
      cksum_hmac_sha256_final(cks, binres, sizeof(binres));
    } else {
      sha256_ctx *cks = (sha256_ctx *)cksi->ptr;
      cksum_sha256_final(cks, binres);
    }
    imax = ((int)rsize  - 1) / 2; 
    if (imax > SHA256_DIGEST_SIZE) { imax = SHA256_DIGEST_SIZE; }
    for (i1 = 0; i1 < imax; i1++) { sprintf(result + i1 * 2, "%02x", (unsigned int)binres[i1]); }

  } else if (cksi->digest == SML3_CKSUM_DIGEST_SHA2_384) {  /* SHA-2-384 */
    unsigned char binres[SHA384_DIGEST_SIZE];
    if (cksi->hmac) {
      hmac_sha384_ctx *cks = (hmac_sha384_ctx *)cksi->ptr;
      cksum_hmac_sha384_final(cks, binres, sizeof(binres));
    } else {
      sha384_ctx *cks = (sha384_ctx *)cksi->ptr;
      cksum_sha384_final(cks, binres);
    }
    imax = ((int)rsize  - 1) / 2; 
    if (imax > SHA384_DIGEST_SIZE) { imax = SHA384_DIGEST_SIZE; }
    for (i1 = 0; i1 < imax; i1++) { sprintf(result + i1 * 2, "%02x", (unsigned int)binres[i1]); }

  } else if (cksi->digest == SML3_CKSUM_DIGEST_SHA2_512) {  /* SHA-2-512 */
    unsigned char binres[SHA512_DIGEST_SIZE];
    if (cksi->hmac) {
      hmac_sha512_ctx *cks = (hmac_sha512_ctx *)cksi->ptr;
      cksum_hmac_sha512_final(cks, binres, sizeof(binres));
    } else {
      sha512_ctx *cks = (sha512_ctx *)cksi->ptr;
      cksum_sha512_final(cks, binres);
    }
    imax = ((int)rsize  - 1) / 2; 
    if (imax > SHA512_DIGEST_SIZE) { imax = SHA512_DIGEST_SIZE; }
    for (i1 = 0; i1 < imax; i1++) { sprintf(result + i1 * 2, "%02x", (unsigned int)binres[i1]); }
  }

  free(cksi->ptr);
  free(cksi);

  return result;
} /* Ende SML3_cksum_result */
