/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */

/***********************************************************************
**
** Name: tmocon.c
**
** Description: test client socket connection.
**
** Modification History:
** 19-May-97 AGarcia- Converted the test to accomodate the debug_mode flag.
**           The debug mode will print all of the printfs associated with this
*test.
**           The regress mode will be the default mode. Since the regress tool
*limits
**           the output to a one line status:PASS or FAIL,all of the printf
*statements
**           have been handled with an if (debug_mode) statement.
***********************************************************************/

/***********************************************************************
** Includes
***********************************************************************/
/* Used to get the command line option */
#include "plgetopt.h"

#include "nspr.h"
#include "pprio.h"

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

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

/* for getcwd */
#if defined(XP_UNIX)
#  include <unistd.h>
#elif defined(XP_PC)
#  include <direct.h>
#endif

#ifdef WINCE
#  include <windows.h>
char* getcwd(char* buf, size_t size) {
  wchar_t wpath[MAX_PATH];
  _wgetcwd(wpath, MAX_PATH);
  WideCharToMultiByte(CP_ACP, 0, wpath, -1, buf, size, 0, 0);
}
#endif

#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_DALLY 1
#define DEFAULT_THREADS 1
#define DEFAULT_TIMEOUT 10
#define DEFAULT_MESSAGES 100
#define DEFAULT_MESSAGESIZE 100

static PRFileDesc* debug_out = NULL;

typedef struct Shared {
  PRBool random;
  PRBool failed;
  PRBool intermittant;
  PRIntn debug;
  PRInt32 messages;
  PRIntervalTime dally;
  PRIntervalTime timeout;
  PRInt32 message_length;
  PRNetAddr serverAddress;
} Shared;

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

static void CauseTimeout(const Shared* shared) {
  if (shared->intermittant) {
    PR_Sleep(Timeout(shared));
  }
} /* CauseTimeout */

static PRStatus MakeReceiver(Shared* shared) {
  PRStatus rv = PR_FAILURE;
  if (PR_IsNetAddrType(&shared->serverAddress, PR_IpAddrLoopback)) {
    char* argv[3];
    char path[1024 + sizeof("/tmoacc")];

    getcwd(path, sizeof(path));

    (void)strcat(path, "/tmoacc");
#ifdef XP_PC
    (void)strcat(path, ".exe");
#endif
    argv[0] = path;
    if (shared->debug > 0) {
      argv[1] = "-d";
      argv[2] = NULL;
    } else {
      argv[1] = NULL;
    }
    if (shared->debug > 1) {
      PR_fprintf(debug_out, " creating accept process %s ...", path);
    }
    fflush(stdout);
    rv = PR_CreateProcessDetached(path, argv, NULL, NULL);
    if (PR_SUCCESS == rv) {
      if (shared->debug > 1) {
        PR_fprintf(debug_out, " wait 5 seconds");
      }
      if (shared->debug > 1) {
        PR_fprintf(debug_out, " before connecting to accept process ...");
      }
      fflush(stdout);
      PR_Sleep(PR_SecondsToInterval(5));
      return rv;
    }
    shared->failed = PR_TRUE;
    if (shared->debug > 0) {
      PL_FPrintError(debug_out, "PR_CreateProcessDetached failed");
    }
  }
  return rv;
} /* MakeReceiver */

static void Connect(void* arg) {
  PRStatus rv;
  char* buffer = NULL;
  PRFileDesc* clientSock;
  Shared* shared = (Shared*)arg;
  PRInt32 loop, bytes, flags = 0;
  struct Descriptor {
    PRInt32 length;
    PRUint32 checksum;
  } descriptor;
  debug_out = (0 == shared->debug) ? NULL : PR_GetSpecialFD(PR_StandardError);

  buffer = (char*)PR_MALLOC(shared->message_length);

  for (bytes = 0; bytes < shared->message_length; ++bytes) {
    buffer[bytes] = (char)bytes;
  }

  descriptor.checksum = 0;
  for (bytes = 0; bytes < shared->message_length; ++bytes) {
    PRUint32 overflow = descriptor.checksum & 0x80000000;
    descriptor.checksum = (descriptor.checksum << 1);
    if (0x00000000 != overflow) {
      descriptor.checksum += 1;
    }
    descriptor.checksum += buffer[bytes];
  }
  descriptor.checksum = PR_htonl(descriptor.checksum);

  for (loop = 0; loop < shared->messages; ++loop) {
    if (shared->debug > 1) {
      PR_fprintf(debug_out, "[%d]socket ... ", loop);
    }
    clientSock = PR_NewTCPSocket();
    if (clientSock) {
      /*
       * We need to slow down the rate of generating connect requests,
       * otherwise the listen backlog queue on the accept side may
       * become full and we will get connection refused or timeout
       * error.
       */

      PR_Sleep(shared->dally);
      if (shared->debug > 1) {
        char buf[128];
        PR_NetAddrToString(&shared->serverAddress, buf, sizeof(buf));
        PR_fprintf(debug_out, "connecting to %s ... ", buf);
      }
      rv = PR_Connect(clientSock, &shared->serverAddress, Timeout(shared));
      if (PR_SUCCESS == rv) {
        PRInt32 descriptor_length =
            (loop < (shared->messages - 1)) ? shared->message_length : 0;
        descriptor.length = PR_htonl(descriptor_length);
        if (shared->debug > 1)
          PR_fprintf(debug_out, "sending %d bytes ... ", descriptor_length);
        CauseTimeout(shared); /* might cause server to timeout */
        bytes = PR_Send(clientSock, &descriptor, sizeof(descriptor), flags,
                        Timeout(shared));
        if (bytes != sizeof(descriptor)) {
          shared->failed = PR_TRUE;
          if (shared->debug > 0) {
            PL_FPrintError(debug_out, "PR_Send failed");
          }
        }
        if (0 != descriptor_length) {
          CauseTimeout(shared);
          bytes = PR_Send(clientSock, buffer, descriptor_length, flags,
                          Timeout(shared));
          if (bytes != descriptor_length) {
            shared->failed = PR_TRUE;
            if (shared->debug > 0) {
              PL_FPrintError(debug_out, "PR_Send failed");
            }
          }
        }
        if (shared->debug > 1) {
          PR_fprintf(debug_out, "closing ... ");
        }
        rv = PR_Shutdown(clientSock, PR_SHUTDOWN_BOTH);
        rv = PR_Close(clientSock);
        if (shared->debug > 1) {
          if (PR_SUCCESS == rv) {
            PR_fprintf(debug_out, "\n");
          } else {
            PL_FPrintError(debug_out, "shutdown failed");
          }
        }
      } else {
        if (shared->debug > 1) {
          PL_FPrintError(debug_out, "connect failed");
        }
        PR_Close(clientSock);
        if ((loop == 0) && (PR_GetError() == PR_CONNECT_REFUSED_ERROR)) {
          if (MakeReceiver(shared) == PR_FAILURE) {
            break;
          }
        } else {
          if (shared->debug > 1) {
            PR_fprintf(debug_out, " exiting\n");
          }
          break;
        }
      }
    } else {
      shared->failed = PR_TRUE;
      if (shared->debug > 0) {
        PL_FPrintError(debug_out, "create socket");
      }
      break;
    }
  }

  PR_DELETE(buffer);
} /* Connect */

int Tmocon(int argc, char** argv) {
  /*
   * USAGE
   * -d       turn on debugging output                (default = off)
   * -v       turn on verbose output                  (default = off)
   * -h <n>   dns name of host serving the connection (default = self)
   * -i       dally intermittantly to cause timeouts  (default = off)
   * -m <n>   number of messages to send              (default = 100)
   * -s <n>   size of each message                    (default = 100)
   * -t <n>   number of threads sending               (default = 1)
   * -G       use global threads                      (default = local)
   * -T <n>   timeout on I/O operations (seconds)     (default = 10)
   * -D <n>   dally between connect requests (seconds)(default = 0)
   * -R       randomize the dally types around 'T'    (default = no)
   */

  PRStatus rv;
  int exitStatus;
  PLOptStatus os;
  Shared* shared = NULL;
  PRThread** thread = NULL;
  PRIntn index, threads = DEFAULT_THREADS;
  PRThreadScope thread_scope = PR_LOCAL_THREAD;
  PRInt32 dally = DEFAULT_DALLY, timeout = DEFAULT_TIMEOUT;
  PLOptState* opt = PL_CreateOptState(argc, argv, "divGRh:m:s:t:T:D:");

  shared = PR_NEWZAP(Shared);

  shared->debug = 0;
  shared->failed = PR_FALSE;
  shared->random = PR_FALSE;
  shared->messages = DEFAULT_MESSAGES;
  shared->message_length = DEFAULT_MESSAGESIZE;

  memset(&shared->serverAddress, 0, sizeof(shared->serverAddress));
  rv = PR_InitializeNetAddr(PR_IpAddrLoopback, BASE_PORT,
                            &shared->serverAddress);
  PR_ASSERT(PR_SUCCESS == rv);

  while (PL_OPT_EOL != (os = PL_GetNextOpt(opt))) {
    if (PL_OPT_BAD == os) {
      continue;
    }
    switch (opt->option) {
      case 'd':
        if (0 == shared->debug) {
          shared->debug = 1;
        }
        break;
      case 'v':
        if (0 == shared->debug) {
          shared->debug = 2;
        }
        break;
      case 'i':
        shared->intermittant = PR_TRUE;
        break;
      case 'R':
        shared->random = PR_TRUE;
        break;
      case 'G':
        thread_scope = PR_GLOBAL_THREAD;
        break;
      case 'h': /* the value for backlock */
      {
        PRIntn es = 0;
        PRHostEnt host;
        char buffer[1024];
        (void)PR_GetHostByName(opt->value, buffer, sizeof(buffer), &host);
        es = PR_EnumerateHostEnt(es, &host, BASE_PORT, &shared->serverAddress);
        PR_ASSERT(es > 0);
      } break;
      case 'm': /* number of messages to send */
        shared->messages = atoi(opt->value);
        break;
      case 't': /* number of threads sending */
        threads = atoi(opt->value);
        break;
      case 'D': /* dally time between transmissions */
        dally = atoi(opt->value);
        break;
      case 'T': /* timeout on I/O operations */
        timeout = atoi(opt->value);
        break;
      case 's': /* total size of each message */
        shared->message_length = atoi(opt->value);
        break;
      default:
        break;
    }
  }
  PL_DestroyOptState(opt);

  if (0 == timeout) {
    timeout = DEFAULT_TIMEOUT;
  }
  if (0 == threads) {
    threads = DEFAULT_THREADS;
  }
  if (0 == shared->messages) {
    shared->messages = DEFAULT_MESSAGES;
  }
  if (0 == shared->message_length) {
    shared->message_length = DEFAULT_MESSAGESIZE;
  }

  shared->dally = PR_SecondsToInterval(dally);
  shared->timeout = PR_SecondsToInterval(timeout);

  thread = (PRThread**)PR_CALLOC(threads * sizeof(PRThread*));

  for (index = 0; index < threads; ++index)
    thread[index] =
        PR_CreateThread(PR_USER_THREAD, Connect, shared, PR_PRIORITY_NORMAL,
                        thread_scope, PR_JOINABLE_THREAD, 0);
  for (index = 0; index < threads; ++index) {
    rv = PR_JoinThread(thread[index]);
  }

  PR_DELETE(thread);

  PR_fprintf(PR_GetSpecialFD(PR_StandardError), "%s\n",
             ((shared->failed) ? "FAILED" : "PASSED"));
  exitStatus = (shared->failed) ? 1 : 0;
  PR_DELETE(shared);
  return exitStatus;
}

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

/* tmocon.c */
