/*-
 ***********************************************************************
 *
 * $Id: string2pool.c,v 1.18 2012/01/07 07:56:14 mavrik Exp $
 *
 ***********************************************************************
 *
 * Copyright 2002-2012 The WebJob Project, All Rights Reserved.
 *
 ***********************************************************************
 */
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef WIN32
#include <windows.h>
#else
#include <unistd.h>
#include <sys/types.h>
#endif

/*-
 ***********************************************************************
 *
 * Defines
 *
 ***********************************************************************
 */
#ifdef WIN32
#define strcasecmp _stricmp
#endif

#define PROGRAM           "string2pool"
#define STRING2POOL_MAX_TRIES       10
#define STRING2POOL_POOL_SIZE   0x8000
#define STRING2POOL_PREFIX_SIZE     32
#define STRING2POOL_SUFFIX_SIZE     32

#define STRING2POOL_SCHEME_BASE64    0
#define STRING2POOL_SCHEME_LRS_1     1

/*-
 ***********************************************************************
 *
 * Macros
 *
 ***********************************************************************
 */
#define STRING2POOL_NEW_POOL(aucPool, iLength, ulState)\
{\
  int i, j;\
  unsigned long ul = ulState;\
  for (i = 0; i < iLength; i++)\
  {\
    for (j = 0, aucPool[i] = 0; j < 8; j++)\
    {\
      aucPool[i] |= (ul & 1) << j;\
      ul = ((((ul >> 7) ^ (ul >> 6) ^ (ul >> 2) ^ (ul >> 0)) & 1) << 31) | (ul >> 1);\
    }\
  }\
  ulState = ul; /* Return the last register state -- allows the caller to continue the sequence. */\
}


/*-
 ***********************************************************************
 *
 * ConstructIncludeFile
 *
 ***********************************************************************
 */
void
ConstructIncludeFile(FILE *pFile, char *pcPoolType, unsigned long ulFinal, int *piTaps, int iTapsCount)
{
  char                acPrefix[STRING2POOL_PREFIX_SIZE];
  char                acSuffix[STRING2POOL_SUFFIX_SIZE];
  int                 i;

  for (i = 0; i < (int)strlen(pcPoolType); i++)
  {
    acPrefix[i] = tolower((int)pcPoolType[i]);
  }
  acPrefix[i] = 0;

  if (strcasecmp(pcPoolType, "POOL2STRING") == 0)
  {
    acSuffix[0] = 0;
  }
  else
  {
    strcpy(acSuffix, "-pool");
  }

  fprintf(pFile,
"\
/*-\n\
 ***********************************************************************\n\
 *\n\
 * $%s: %s%s.h,v custom unknown Exp $\n\
 *\n\
 ***********************************************************************\n\
 *\n\
 * Copyright 2003-2012 The WebJob Project, All Rights Reserved.\n\
 *\n\
 ***********************************************************************\n\
 */\n\
#ifndef _%s_POOL_H_INCLUDED\n\
#define _%s_POOL_H_INCLUDED\n\
\n\
/*\n\
 ***********************************************************************\n\
 *\n\
 * Defines\n\
 *\n\
 ***********************************************************************\n\
 */\n\
#define %s_POOL_SEED 0x%08lx\n\
#define %s_POOL_SIZE 0x%08x\n\
#define %s_POOL_TAPS (%d) + (1)\n\
\n\
/*\n\
 ***********************************************************************\n\
 *\n\
 * Macros\n\
 *\n\
 ***********************************************************************\n\
 */\n\
#define %s_NEW_POOL(aucPool, iLength, ulState)\\\n\
{\\\n\
  int i, j;\\\n\
  unsigned long ul = ulState;\\\n\
  for (i = 0; i < iLength; i++)\\\n\
  {\\\n\
    for (j = 0, aucPool[i] = 0; j < 8; j++)\\\n\
    {\\\n\
      aucPool[i] |= (ul & 1) << j;\\\n\
      ul = ((((ul >> 7) ^ (ul >> 6) ^ (ul >> 2) ^ (ul >> 0)) & 1) << 31) | (ul >> 1);\\\n\
    }\\\n\
  }\\\n\
}\n\
\n\
#define %s_TAP_POOL(aucTaps, aucPool)\\\n\
{\\\n\
",
"Id",
acPrefix,
acSuffix,
pcPoolType,
pcPoolType,
pcPoolType,
ulFinal,
pcPoolType,
STRING2POOL_POOL_SIZE,
pcPoolType,
iTapsCount,
pcPoolType,
pcPoolType
);
  if (iTapsCount > 0)
  {
    fprintf(pFile, "  if (%s_POOL_SEED == 0x%08lx && %s_POOL_SIZE == 0x%08x && %s_POOL_TAPS == %d)\\\n",
            pcPoolType,
            ulFinal,
            pcPoolType,
            STRING2POOL_POOL_SIZE,
            pcPoolType,
            iTapsCount + 1
           );
    fprintf(pFile, "  {\\\n");
    for (i = 0; piTaps && i < iTapsCount; i++)
    {
      fprintf(pFile, "    aucTaps[%d] = aucPool[%d];\\\n", i, piTaps[i]);
      fprintf(pFile, "    aucPool[%d] = 0;\\\n", i);
    }
    fprintf(pFile,
"\
    aucTaps[%d] = 0;\\\n\
  }\\\n\
  else\\\n\
  {\\\n\
    aucTaps[0] = 0;\\\n\
  }\\\n\
",
i
);
  }
  else
  {
    fprintf(pFile, "  aucTaps[0] = 0;\\\n");
  }
  fprintf(pFile,
"\
}\n\
\n\
#endif /* !_%s_POOL_H_INCLUDED */\n\
",
pcPoolType
);
  return;
}


/*-
 ***********************************************************************
 *
 * DecodeString
 *
 ***********************************************************************
 */
int
DecodeString(FILE *pFile, int iScheme, char *pcB64String)
{
  char               *pc = NULL;
  char               *pcEnd = NULL;
  char               *pcString = NULL;
  char               *pcTmpString = NULL;
  char               *pcOutString = NULL;
  int                 iLength = 0;
  int                 n = 0;
  unsigned char       aucPool[STRING2POOL_POOL_SIZE] = { 0 };
  unsigned long       ulState = 0;
  unsigned long       ulTap = 0;

  Base64BootStrap();

  iLength = strlen(pcB64String);
  pcTmpString = malloc(iLength + 1);
  if (pcTmpString == NULL)
  {
    fprintf(stderr, "Unable to allocate memory (%s).\n", strerror(errno));
    return -1;
  }
  n = Base64Decode(pcB64String, (unsigned) pcTmpString, iLength);
  pcTmpString[n] = 0;

  switch (iScheme)
  {
  case STRING2POOL_SCHEME_BASE64:
    fprintf(pFile, "%s\n", pcTmpString);
    free(pcTmpString);
    break;
  case STRING2POOL_SCHEME_LRS_1:
    pc = strstr(pcTmpString, ":");
    if (pc == NULL)
    {
      fprintf(stderr, "Invalid format for the specified encoding scheme.\n");
      return -1;
    }
    *pc++ = 0;
    ulState = strtoul(pcTmpString, &pcEnd, 16);
    if (*pcEnd != 0 || errno == ERANGE || ulState == 0)
    {
      fprintf(stderr, "Invalid seed or value could not be converted to a number.\n");
      return -1;
    }
    pcString = pc;
    STRING2POOL_NEW_POOL(aucPool, STRING2POOL_POOL_SIZE, ulState);
    pcOutString = malloc(iLength + 1);
    if (pcOutString == NULL)
    {
      fprintf(stderr, "Unable to allocate memory (%s).\n", strerror(errno));
      return -1;
    }
    for (n = 0, pc = strtok(pcString, ","); pc != NULL; pc = strtok(NULL, ","))
    {
      pcEnd = NULL;
      ulTap = strtoul(pc, &pcEnd, 10);
      if (*pcEnd != 0 || errno == ERANGE || ulTap < 0 || ulTap > STRING2POOL_POOL_SIZE)
      {
        fprintf(stderr, "Invalid tap or value could not be converted to a number.\n");
        return -1;
      }
      n += sprintf(&pcOutString[n], "%c", aucPool[ulTap]);
    }
    pcOutString[n] = 0;
    fprintf(pFile, "%s\n", pcOutString);
    free(pcTmpString);
    free(pcOutString);
    break;
  default:
    fprintf(stderr, "Invalid encoding scheme (%d).\n", iScheme);
    free(pcTmpString);
    return -1;
    break;
  }

  return 0;
}


/*-
 ***********************************************************************
 *
 * EncodeString
 *
 ***********************************************************************
 */
int
EncodeString(FILE *pFile, unsigned long ulFinal, int *piTaps, int iTapsCount)
{
  char               *pcB64String = NULL;
  char               *pcOutString = NULL;
  int                 i = 0;
  int                 iLength = 0;
  int                 n = 0;

  if (iTapsCount > 0)
  {
    iLength = strlen("0xffffffff:") + (iTapsCount * (strlen("4294967295") + 1));
    pcOutString = malloc(iLength + 1);
    if (pcOutString == NULL)
    {
      fprintf(stderr, "Unable to allocate memory (%s).\n", strerror(errno));
      return -1;
    }
    pcB64String = malloc((iLength * 4 / 3) + (iLength % 3) + 1);
    if (pcB64String == NULL)
    {
      fprintf(stderr, "Unable to allocate memory (%s).\n", strerror(errno));
      return -1;
    }
    n = sprintf(&pcOutString[n], "0x%08x:", ulFinal);
    for (i = 0; piTaps && i < iTapsCount; i++)
    {
      n += sprintf(&pcOutString[n], "%s%d", ((i == 0) ? "" : ","), piTaps[i]);
    }
    pcOutString[n] = 0;
    Base64Encode(pcOutString, pcB64String, n);
    fprintf(pFile, "%d:%s\n", STRING2POOL_SCHEME_LRS_1, pcB64String);
  }

  return 0;
}


/*-
 ***********************************************************************
 *
 * Usage
 *
 ***********************************************************************
 */
void
Usage()
{
  fprintf(stderr, "\n");
  fprintf(stderr, "Usage: %s --default prefix\n", PROGRAM);
  fprintf(stderr, "       %s --defined prefix seed string\n", PROGRAM);
  fprintf(stderr, "       %s --encode string\n", PROGRAM);
  fprintf(stderr, "       %s --decode string\n", PROGRAM);
  fprintf(stderr, "\n");
  exit(1);
}


/*-
 ***********************************************************************
 *
 * Main
 *
 ***********************************************************************
 */
int
main(int iArgumentCount, char *ppcArgumentVector[])
{
  const char          acRoutine[] = "Main()";
  char                acPrefix[STRING2POOL_PREFIX_SIZE];
  char               *pcEnd;
  char               *pcString;
  int                 i;
  int                 iDone;
  int                 iDecodeOnly = 0;
  int                 iEncodeOnly = 0;
  int                 iScheme;
  int                 iStringLength;
  int                 iStringOffset;
  int                 iTries;
  int                *piTaps;
  unsigned char       aucPool[STRING2POOL_POOL_SIZE];
  unsigned long       ulSeed;
  unsigned long       ulFinal;
  unsigned long       ulState;

  /*-
   *********************************************************************
   *
   * Seed the random number generator.
   *
   *********************************************************************
   */
#ifdef WIN32
  ulSeed = (unsigned long) GetTickCount() ^ (unsigned long) time(NULL);
  srand(ulSeed);
#else
  ulSeed = (((unsigned long) getpid()) << 16) ^ (unsigned long) time(NULL);
  srandom(ulSeed);
#endif

  /*-
   *********************************************************************
   *
   * Process arguments.
   *
   *********************************************************************
   */
  if (iArgumentCount == 3)
  {
    if (strcasecmp(ppcArgumentVector[1], "--default") == 0)
    {
      if (strcasecmp(ppcArgumentVector[2], "DSV") == 0)
      {
        strcpy(acPrefix, "DSV");
      }
      else if (strcasecmp(ppcArgumentVector[2], "HTTP") == 0)
      {
        strcpy(acPrefix, "HTTP");
      }
      else if (strcasecmp(ppcArgumentVector[2], "POOL") == 0)
      {
        strcpy(acPrefix, "POOL2STRING");
      }
      else if (strcasecmp(ppcArgumentVector[2], "SSL") == 0)
      {
        strcpy(acPrefix, "SSL");
      }
      else
      {
        fprintf(stderr, "%s: Prefix='%s': Prefix must be one of 'DSV', 'HTTP', 'POOL', or 'SSL'.\n", acRoutine, ppcArgumentVector[2]);
        return 2;
      }
      ConstructIncludeFile(stdout, acPrefix, 0, NULL, 0);
      return 0;
    }
    else if (strcasecmp(ppcArgumentVector[1], "--decode") == 0)
    {
      if (ppcArgumentVector[2][0] != 0)
      {
        char *pc = NULL;
        iStringLength = strlen(ppcArgumentVector[2]);
        pcString = malloc(iStringLength + 1);
        if (pcString == NULL)
        {
          fprintf(stderr, "Unable to allocate memory (%s).\n", strerror(errno));
          return 2;
        }
        strncpy(pcString, ppcArgumentVector[2], iStringLength + 1);
        pc = strstr(pcString, ":");
        if (pc == NULL)
        {
          fprintf(stderr, "Encoded string is not in the proper format.\n");
          return 2;
        }
        *pc++ = 0;
        iScheme = strtol(pcString, &pcEnd, 10);
        if (*pcEnd != 0 || errno == ERANGE)
        {
          fprintf(stderr, "Algorithm (%s) could not be converted to a 32-bit decimal number.\n", pcString);
          return 2;
        }
        pcString = pc;
      }
      iDecodeOnly = 1;
    }
    else if (strcasecmp(ppcArgumentVector[1], "--encode") == 0)
    {
#ifdef WIN32
      ulState = (unsigned long) ((GetTickCount() << 16) ^ rand());
#else
      ulState = (unsigned long) random();
#endif
      pcString = ppcArgumentVector[2];
      iStringLength = strlen(pcString);
      if (iStringLength > (STRING2POOL_POOL_SIZE / 1024))
      {
        fprintf(stderr, "%s: PoolSize='%d' String='%s' Length='%d': Required Pool size is (Length * 1024).\n", acRoutine, STRING2POOL_POOL_SIZE, ppcArgumentVector[2], iStringLength);
        return 2;
      }
      piTaps = malloc(iStringLength * sizeof(int));
      if (piTaps == NULL)
      {
        fprintf(stderr, "%s: malloc(): %s\n", acRoutine, strerror(errno));
        return 2;
      }
      iEncodeOnly = 1;
    }
    else
    {
      Usage();
    }
  }
  else if (iArgumentCount == 5)
  {
    if (strcasecmp(ppcArgumentVector[1], "--defined") == 0)
    {
      if (strcasecmp(ppcArgumentVector[2], "DSV") == 0)
      {
        strcpy(acPrefix, "DSV");
      }
      else if (strcasecmp(ppcArgumentVector[2], "HTTP") == 0)
      {
        strcpy(acPrefix, "HTTP");
      }
      else if (strcasecmp(ppcArgumentVector[2], "POOL") == 0)
      {
        strcpy(acPrefix, "POOL2STRING");
      }
      else if (strcasecmp(ppcArgumentVector[2], "SSL") == 0)
      {
        strcpy(acPrefix, "SSL");
      }
      else
      {
        fprintf(stderr, "%s: Prefix='%s': Prefix must be one of 'DSV', 'HTTP', 'POOL', or 'SSL'.\n", acRoutine, ppcArgumentVector[2]);
        return 2;
      }
      if (ppcArgumentVector[3][0] != 0)
      {
        ulState = strtoul(ppcArgumentVector[3], &pcEnd, 16);
        if (*pcEnd != 0 || errno == ERANGE)
        {
          fprintf(stderr, "%s: Seed='%s': Value could not be converted to a 32 bit hex number.\n", acRoutine, ppcArgumentVector[3]);
          return 3;
        }
        if (ulState == 0)
        {
#ifdef WIN32
          ulState = (unsigned long) ((GetTickCount() << 16) ^ rand());
#else
          ulState = (unsigned long) random();
#endif
        }
      }
      else
      {
        fprintf(stderr, "%s: Seed='%s': Value must not be null.\n", acRoutine, ppcArgumentVector[3]);
        return 4;
      }
      pcString = ppcArgumentVector[4];
      iStringLength = strlen(pcString);
      if (iStringLength > (STRING2POOL_POOL_SIZE / 1024))
      {
        fprintf(stderr, "%s: PoolSize='%d' String='%s' Length='%d': Required Pool size is (Length * 1024).\n", acRoutine, STRING2POOL_POOL_SIZE, ppcArgumentVector[4], iStringLength);
        return 5;
      }
      piTaps = malloc(iStringLength * sizeof(int));
      if (piTaps == NULL)
      {
        fprintf(stderr, "%s: malloc(): %s\n", acRoutine, strerror(errno));
        return 6;
      }
    }
    else
    {
      Usage();
    }
  }
  else
  {
    Usage();
  }

  /*-
   *********************************************************************
   *
   * Conditionally decode the string and write it out.
   *
   *********************************************************************
   */
  if (iDecodeOnly)
  {
    if (DecodeString(stdout, iScheme, pcString) != 0)
    {
      return 2;
    }
    return 0;
  }

  /*-
   *********************************************************************
   *
   * Locate a pool that contains the specified string.
   *
   *********************************************************************
   */
  for (iDone = iTries = iStringOffset = 0; !iDone && iTries < STRING2POOL_MAX_TRIES; iTries++, iStringOffset = 0)
  {
    ulFinal = ulState;
    STRING2POOL_NEW_POOL(aucPool, STRING2POOL_POOL_SIZE, ulState);
    for (i = 0; i < STRING2POOL_POOL_SIZE && iStringOffset < iStringLength; i++)
    {
      if (aucPool[i] == pcString[iStringOffset])
      {
        piTaps[iStringOffset++] = i;
      }
    }
    iDone = (iStringOffset == iStringLength) ? 1 : 0;
  }
  if (iTries >= STRING2POOL_MAX_TRIES)
  {
    fprintf(stderr, "%s: Tries='%d': Failed to locate a suitable pool within the given number of tries.\n", acRoutine, iTries);
    return 7;
  }

  /*-
   *********************************************************************
   *
   * Conditionally encode the string and write it out.
   *
   *********************************************************************
   */
  if (iEncodeOnly)
  {
    if (EncodeString(stdout, ulFinal, piTaps, iStringLength) != 0)
    {
      return 2;
    }
    return 0;
  }

  /*-
   *********************************************************************
   *
   * Construct an include file and write it out.
   *
   *********************************************************************
   */
  ConstructIncludeFile(stdout, acPrefix, ulFinal, piTaps, iStringLength);

  return 0;
}
