/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nspr.h"

#include <stdlib.h>
#include <string.h>

#include "plerror.h"
#include "plgetopt.h"

#ifdef DEBUG
#  define PORT_INC_DO +100
#else
#  define PORT_INC_DO
#endif
#ifdef IS_64
#  define PORT_INC_3264 +200
#else
#  define PORT_INC_3264
#endif

#define BASE_PORT 9867 PORT_INC_DO PORT_INC_3264
#define DEFAULT_THREADS 1
#define DEFAULT_BACKLOG 10
#define DEFAULT_TIMEOUT 10
#define RANDOM_RANGE 100 /* should be significantly smaller than RAND_MAX */

typedef enum { running, stopped } Status;

typedef struct Shared {
  PRLock* ml;
  PRCondVar* cv;
  PRBool passed;
  PRBool random;
  PRFileDesc* debug;
  PRIntervalTime timeout;
  PRFileDesc* listenSock;
  Status status;
} Shared;

static PRIntervalTime Timeout(const Shared* shared) {
  PRIntervalTime timeout = shared->timeout;
  if (shared->random) {
    PRIntervalTime half = timeout >> 1; /* one half of the interval */
    PRIntervalTime quarter = half >> 1; /* one quarter of the interval */
    /* something in [0..timeout / 2) */
    PRUint32 random = (rand() % RANDOM_RANGE) * half / RANDOM_RANGE;
    timeout = (3 * quarter) + random; /* [75..125)% */
  }
  return timeout;
} /* Timeout */

static void Accept(void* arg) {
  PRStatus rv;
  char* buffer = NULL;
  PRNetAddr clientAddr;
  Shared* shared = (Shared*)arg;
  PRInt32 recv_length = 0, flags = 0;
  PRFileDesc* clientSock;
  PRIntn toread, byte, bytes, loop = 0;
  struct Descriptor {
    PRInt32 length;
    PRUint32 checksum;
  } descriptor;

  do {
    PRUint32 checksum = 0;
    if (NULL != shared->debug) {
      PR_fprintf(shared->debug, "[%d]accepting ... ", loop++);
    }
    clientSock = PR_Accept(shared->listenSock, &clientAddr, Timeout(shared));
    if (clientSock != NULL) {
      if (NULL != shared->debug) {
        PR_fprintf(shared->debug, "reading length ... ");
      }
      bytes = PR_Recv(clientSock, &descriptor, sizeof(descriptor), flags,
                      Timeout(shared));
      if (sizeof(descriptor) == bytes) {
        /* and, before doing something stupid ... */
        descriptor.length = PR_ntohl(descriptor.length);
        descriptor.checksum = PR_ntohl(descriptor.checksum);
        if (NULL != shared->debug) {
          PR_fprintf(shared->debug, "%d bytes ... ", descriptor.length);
        }
        toread = descriptor.length;
        if (recv_length < descriptor.length) {
          if (NULL != buffer) {
            PR_DELETE(buffer);
          }
          buffer = (char*)PR_MALLOC(descriptor.length);
          recv_length = descriptor.length;
        }
        for (toread = descriptor.length; toread > 0; toread -= bytes) {
          bytes = PR_Recv(clientSock, &buffer[descriptor.length - toread],
                          toread, flags, Timeout(shared));
          if (-1 == bytes) {
            if (NULL != shared->debug) {
              PR_fprintf(shared->debug, "read data failed...");
            }
            bytes = 0;
          }
        }
      } else if (NULL != shared->debug) {
        PR_fprintf(shared->debug, "read desciptor failed...");
        descriptor.length = -1;
      }
      if (NULL != shared->debug) {
        PR_fprintf(shared->debug, "closing");
      }
      rv = PR_Shutdown(clientSock, PR_SHUTDOWN_BOTH);
      if ((PR_FAILURE == rv) && (NULL != shared->debug)) {
        PR_fprintf(shared->debug, " failed");
        shared->passed = PR_FALSE;
      }
      rv = PR_Close(clientSock);
      if (PR_FAILURE == rv)
        if (NULL != shared->debug) {
          PR_fprintf(shared->debug, " failed");
          shared->passed = PR_FALSE;
        }
      if (descriptor.length > 0) {
        for (byte = 0; byte < descriptor.length; ++byte) {
          PRUint32 overflow = checksum & 0x80000000;
          checksum = (checksum << 1);
          if (0x00000000 != overflow) {
            checksum += 1;
          }
          checksum += buffer[byte];
        }
        if ((descriptor.checksum != checksum) && (NULL != shared->debug)) {
          PR_fprintf(shared->debug, " ... data mismatch");
          shared->passed = PR_FALSE;
        }
      } else if (0 == descriptor.length) {
        PR_Lock(shared->ml);
        shared->status = stopped;
        PR_NotifyCondVar(shared->cv);
        PR_Unlock(shared->ml);
      }
      if (NULL != shared->debug) {
        PR_fprintf(shared->debug, "\n");
      }
    } else {
      if (PR_PENDING_INTERRUPT_ERROR != PR_GetError()) {
        if (NULL != shared->debug) {
          PL_PrintError("Accept");
        }
        shared->passed = PR_FALSE;
      }
    }
  } while (running == shared->status);
  if (NULL != buffer) {
    PR_DELETE(buffer);
  }
} /* Accept */

PRIntn Tmoacc(PRIntn argc, char** argv) {
  PRStatus rv;
  PRIntn exitStatus;
  PRIntn index;
  Shared* shared;
  PLOptStatus os;
  PRThread** thread;
  PRNetAddr listenAddr;
  PRSocketOptionData sockOpt;
  PRIntn timeout = DEFAULT_TIMEOUT;
  PRIntn threads = DEFAULT_THREADS;
  PRIntn backlog = DEFAULT_BACKLOG;
  PRThreadScope thread_scope = PR_LOCAL_THREAD;

  PLOptState* opt = PL_CreateOptState(argc, argv, "dGb:t:T:R");

  shared = PR_NEWZAP(Shared);

  shared->debug = NULL;
  shared->passed = PR_TRUE;
  shared->random = PR_TRUE;
  shared->status = running;
  shared->ml = PR_NewLock();
  shared->cv = PR_NewCondVar(shared->ml);

  while (PL_OPT_EOL != (os = PL_GetNextOpt(opt))) {
    if (PL_OPT_BAD == os) {
      continue;
    }
    switch (opt->option) {
      case 'd': /* debug mode */
        shared->debug = PR_GetSpecialFD(PR_StandardError);
        break;
      case 'G': /* use global threads */
        thread_scope = PR_GLOBAL_THREAD;
        break;
      case 'b': /* size of listen backlog */
        backlog = atoi(opt->value);
        break;
      case 't': /* number of threads doing accept */
        threads = atoi(opt->value);
        break;
      case 'T': /* timeout used for network operations */
        timeout = atoi(opt->value);
        break;
      case 'R': /* randomize the timeout values */
        shared->random = PR_TRUE;
        break;
      default:
        break;
    }
  }
  PL_DestroyOptState(opt);
  if (0 == threads) {
    threads = DEFAULT_THREADS;
  }
  if (0 == backlog) {
    backlog = DEFAULT_BACKLOG;
  }
  if (0 == timeout) {
    timeout = DEFAULT_TIMEOUT;
  }

  memset(&listenAddr, 0, sizeof(listenAddr));
  rv = PR_InitializeNetAddr(PR_IpAddrAny, BASE_PORT, &listenAddr);
  PR_ASSERT(PR_SUCCESS == rv);

  shared->timeout = PR_SecondsToInterval(timeout);

  /* First bind to the socket */
  shared->listenSock = PR_NewTCPSocket();
  if (shared->listenSock) {
    sockOpt.option = PR_SockOpt_Reuseaddr;
    sockOpt.value.reuse_addr = PR_TRUE;
    rv = PR_SetSocketOption(shared->listenSock, &sockOpt);
    PR_ASSERT(PR_SUCCESS == rv);
    rv = PR_Bind(shared->listenSock, &listenAddr);
    if (rv != PR_FAILURE) {
      rv = PR_Listen(shared->listenSock, threads + backlog);
      if (PR_SUCCESS == rv) {
        thread = (PRThread**)PR_CALLOC(threads * sizeof(PRThread*));
        for (index = 0; index < threads; ++index) {
          thread[index] = PR_CreateThread(PR_USER_THREAD, Accept, shared,
                                          PR_PRIORITY_NORMAL, thread_scope,
                                          PR_JOINABLE_THREAD, 0);
          PR_ASSERT(NULL != thread[index]);
        }

        PR_Lock(shared->ml);
        while (shared->status == running) {
          PR_WaitCondVar(shared->cv, PR_INTERVAL_NO_TIMEOUT);
        }
        PR_Unlock(shared->ml);
        for (index = 0; index < threads; ++index) {
          rv = PR_Interrupt(thread[index]);
          PR_ASSERT(PR_SUCCESS == rv);
          rv = PR_JoinThread(thread[index]);
          PR_ASSERT(PR_SUCCESS == rv);
        }
        PR_DELETE(thread);
      } else {
        if (shared->debug) {
          PL_PrintError("Listen");
        }
        shared->passed = PR_FALSE;
      }
    } else {
      if (shared->debug) {
        PL_PrintError("Bind");
      }
      shared->passed = PR_FALSE;
    }

    PR_Close(shared->listenSock);
  } else {
    if (shared->debug) {
      PL_PrintError("Create");
    }
    shared->passed = PR_FALSE;
  }

  PR_DestroyCondVar(shared->cv);
  PR_DestroyLock(shared->ml);

  PR_fprintf(PR_GetSpecialFD(PR_StandardError), "%s\n",
             ((shared->passed) ? "PASSED" : "FAILED"));

  exitStatus = (shared->passed) ? 0 : 1;
  PR_DELETE(shared);
  return exitStatus;
}

int main(int argc, char** argv) {
  return (PR_VersionCheck(PR_VERSION)) ? PR_Initialize(Tmoacc, argc, argv, 4)
                                       : -1;
} /* main */

/* tmoacc */
