/*      checksum.cpp
//      
//      Copyright 2005-2011 Lucas Tsatiris <systester.project@gmail.com>
//      
//      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 program; if not, write to the Free Software
//      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
//      MA 02110-1301, USA.
//      
*/


/*
 * Validate the checksum of the result
 */

/*
 * Change the endianess of a short int
 */

#include <math.h>

static unsigned short
swapendian16 (unsigned short oldnum)
{
  char *oldn, *newn;
  unsigned short newnum;
  oldn = (char *) &oldnum;
  newn = (char *) &newnum;
  newn[0] = oldn[1];
  newn[1] = oldn[0];

  return newnum;
}

/*
 * Check the endianess of the machine. 0 LE, 1 BE
 */
static unsigned char
machine_endianess (void)
{
  unsigned char *endian;
  short int testnum = 256;
  endian = (unsigned char *) &testnum;
  return endian[0];
}

/*
 *  16 bit version
 */
static unsigned short
checksum16 (unsigned char *data, int len)
{
  unsigned int sum = 0, tmp;

  if (machine_endianess () == 0)	/* Little Endian */
    {
      if ((len & 1) == 0)
	len = len >> 1;
      else
	len = (len >> 1) + 1;
      while (len > 0)
	{
	  sum += *((unsigned short *) data);
	  data += sizeof (unsigned short);
	  len--;
	}

      sum = (sum >> 16) + (sum & 0xffff);
      sum += (sum >> 16);
      return (~sum);
    }

  /* Big Endian */
  if ((len & 1) == 0)
    len = len / 2;
  else
    len = (len / 2) + 1;

  while (len > 0)
    {
      tmp = swapendian16 (*(unsigned short *) data);
      sum += tmp;
      data += sizeof (unsigned short);
      len--;
    }

  sum = (sum << 16) + (sum & 0xffff0000);
  sum += (sum << 16);
  return (~sum >> 16);
}

/* Checksum validation */
int
validate_checksum (int prec, char *pistr)
{
  int prec_table[11] = {
    131072, 262144, 524288, 1048576, 2097152,
    4194304, 8388608, 16777216, 33554432,
    67108864, 134217728
  }, n;

  unsigned short checksum[11] = {
    21500, 27186, 156, 22100, 14020,
    11126, 19420, 58, 14711, 16470, 60748
  }, validsum;


  validsum = checksum16 ((unsigned char *) pistr, prec);

  n = 0;
loop_top:
  if (prec_table[n] == prec)
    {
      if (validsum == checksum[n])
	return 1;		/* valid checksum - TRUE */
      return 0;			/* invalid checksum - FALSE */
    }

  n++;
  if (n < 11)
    goto loop_top;

  return 0;			/* subscript out of range: return FALSE */
}

/*
 *  Nasty 32bit gcc bug makes this piece of code useless
 *  Re-implemented above using lookup tables
 *  
int
validate_checksum (int prec, char *pistr)
{
  double logbase2;
  int n;
  unsigned short checksum[11] = {
    21500, 27186, 156, 22100, 14020,
    11126, 19420, 58, 14711, 16470, 60748
  }, validsum;
  

  validsum = checksum16 ((unsigned char *) pistr, prec);
  logbase2 = (log10 ((double) prec) / log10 ((double) 2));

// Eqv as the above line but it seems less portable. 
//   FreeBSD rejects it
//   logbase2 = log2 ((double) prec);


  n = (int) logbase2;

  if (validsum == checksum[n - 17])
    return 1;			// valid checksum - TRUE 

  return 0;			    // invalid checksum - FALSE 
}
*/
