/*
     This file is part of GNUnet.
     Copyright (C) 2001, 2002, 2003, 2004, 2006 GNUnet e.V.

     GNUnet is free software: you can redistribute it and/or modify it
     under the terms of the GNU Affero General Public License as published
     by the Free Software Foundation, either version 3 of the License,
     or (at your option) any later version.

     GNUnet 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
     Affero General Public License for more details.

     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later

     For the actual CRC-32 code:
     Copyright abandoned; this code is in the public domain.
     Provided to GNUnet by peter@horizon.com
 */

/**
 * @file util/crypto_crc.c
 * @brief implementation of CRC16 and CRC32
 * @author Christian Grothoff
 */

#include "platform.h"
#include "gnunet_util_lib.h"

#define LOG(kind, ...) GNUNET_log_from (kind, "util-crypto-crc", __VA_ARGS__)

/* Avoid wasting space on 8-byte longs. */
#if UINT_MAX >= 0xffffffff
typedef unsigned int GNUNET_uLong;
#elif ULONG_MAX >= 0xffffffff
typedef unsigned long GNUNET_uLong;
#else
#error This compiler is not ANSI-compliant!
#endif

#define Z_NULL  0


#define POLYNOMIAL (GNUNET_uLong) 0xedb88320
static GNUNET_uLong crc_table[256];

/*
 * This routine writes each crc_table entry exactly once,
 * with the correct final value.  Thus, it is safe to call
 * even on a table that someone else is using concurrently.
 */
static void
crc_init ()
{
  static int once;
  GNUNET_uLong h = 1;

  if (once)
    return;
  once = 1;
  crc_table[0] = 0;
  for (unsigned int i = 128; i; i >>= 1)
  {
    h = (h >> 1) ^ ((h & 1) ? POLYNOMIAL : 0);
    /* h is now crc_table[i] */
    for (unsigned int j = 0; j < 256; j += 2 * i)
      crc_table[i + j] = crc_table[j] ^ h;
  }
}


/*
 * This computes the standard preset and inverted CRC, as used
 * by most networking standards.  Start by passing in an initial
 * chaining value of 0, and then pass in the return value from the
 * previous crc32() call.  The final return value is the CRC.
 * Note that this is a little-endian CRC, which is best used with
 * data transmitted lsbit-first, and it should, itself, be appended
 * to data in little-endian byte and bit order to preserve the
 * property of detecting all burst errors of length 32 bits or less.
 */
static GNUNET_uLong
gn_crc32 (GNUNET_uLong crc, const char *buf, size_t len)
{
  crc_init ();
  GNUNET_assert (crc_table[255] != 0);
  crc ^= 0xffffffff;
  while (len--)
    crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff];
  return crc ^ 0xffffffff;
}


int32_t
GNUNET_CRYPTO_crc32_n (const void *buf, size_t len)
{
  GNUNET_uLong crc;

  crc = gn_crc32 (0L, Z_NULL, 0);
  crc = gn_crc32 (crc, (char *) buf, len);
  return crc;
}


uint32_t
GNUNET_CRYPTO_crc16_step (uint32_t sum, const void *buf, size_t len)
{
  const uint16_t *hdr = buf;

  for (; len >= 2; len -= 2)
    sum += *(hdr++);
  if (len == 1)
    sum += ntohs(*((uint8_t *)hdr) << 8);
  return sum;
}


uint16_t
GNUNET_CRYPTO_crc16_finish (uint32_t sum)
{
  sum = (sum >> 16) + (sum & 0xFFFF);
  sum += (sum >> 16);

  return ~sum;
}


uint16_t
GNUNET_CRYPTO_crc16_n (const void *buf, size_t len)
{
  const uint16_t *hdr = buf;
  uint32_t sum = GNUNET_CRYPTO_crc16_step (0, hdr, len);

  return GNUNET_CRYPTO_crc16_finish (sum);
}


/**
 * @ingroup hash
 * Calculate the checksum of a buffer in one step.
 *
 * @param buf buffer to calculate CRC over
 * @param len number of bytes in @a buf
 * @return crc8 value
 */
uint8_t
GNUNET_CRYPTO_crc8_n (const void *buf,
                      size_t len)
{
  const uint8_t *data = buf;
  unsigned int crc = 0;
  int i;
  int j;

  for (j = len; 0 != j; j--)
  {
    crc ^= (*data++ << 8);
    for (i = 8; 0 != i; i--)
    {
      if (0 != (crc & 0x8000))
        crc ^= (0x1070 << 3);
      crc <<= 1;
    }
  }
  return (uint8_t) (crc >> 8);
}


/* end of crypto_crc.c */
