/* 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: socket.c
**
** Description: Test socket functionality.
**
** Modification History:
*/
#include "primpl.h"

#include "plgetopt.h"

#include <stdio.h>
#include <string.h>
#include <errno.h>
#ifdef XP_UNIX
#  include <sys/mman.h>
#endif
#if defined(_PR_PTHREADS)
#  include <pthread.h>
#endif

#ifdef WIN32
#  include <process.h>
#endif

static int _debug_on = 0;
static int test_cancelio = 0;

#include "obsolete/prsem.h"

#ifdef XP_PC
#  define mode_t int
#endif

#define DPRINTF(arg) \
  if (_debug_on) printf arg

#ifdef XP_PC
char* TEST_DIR = "prdir";
char* SMALL_FILE_NAME = "prsmallf";
char* LARGE_FILE_NAME = "prlargef";
#else
char* TEST_DIR = "./tmp-prsocket_test_dir";
char* SMALL_FILE_NAME = "./tmp-prsocket_test_dir/small_file";
char* LARGE_FILE_NAME = "./tmp-prsocket_test_dir/large_file";
#endif
#define SMALL_FILE_SIZE (3 * 1024) /* 3 KB        */
#define SMALL_FILE_OFFSET_1 (512)
#define SMALL_FILE_LEN_1 (1 * 1024) /* 1 KB        */
#define SMALL_FILE_OFFSET_2 (75)
#define SMALL_FILE_LEN_2 (758)
#define SMALL_FILE_OFFSET_3 (1024)
#define SMALL_FILE_LEN_3 (SMALL_FILE_SIZE - SMALL_FILE_OFFSET_3)
#define SMALL_FILE_HEADER_SIZE (64)   /* 64 bytes    */
#define SMALL_FILE_TRAILER_SIZE (128) /* 128 bytes    */

#define LARGE_FILE_SIZE (3 * 1024 * 1024) /* 3 MB        */
#define LARGE_FILE_OFFSET_1 (0)
#define LARGE_FILE_LEN_1 (2 * 1024 * 1024) /* 2 MB        */
#define LARGE_FILE_OFFSET_2 (64)
#define LARGE_FILE_LEN_2 (1 * 1024 * 1024 + 75)
#define LARGE_FILE_OFFSET_3 (2 * 1024 * 1024 - 128)
#define LARGE_FILE_LEN_3 (LARGE_FILE_SIZE - LARGE_FILE_OFFSET_3)
#define LARGE_FILE_OFFSET_4 PR_GetPageSize()
#define LARGE_FILE_LEN_4 769
#define LARGE_FILE_HEADER_SIZE (512)
#define LARGE_FILE_TRAILER_SIZE (64)

#define BUF_DATA_SIZE (2 * 1024)
#define TCP_MESG_SIZE 1024
/*
 * set UDP datagram size small enough that datagrams sent to a port on the
 * local host will not be lost
 */
#define UDP_DGRAM_SIZE 128
#define NUM_TCP_CLIENTS 5 /* for a listen queue depth of 5 */
#define NUM_UDP_CLIENTS 10

#define NUM_TRANSMITFILE_CLIENTS 4

#define NUM_TCP_CONNECTIONS_PER_CLIENT 5
#define NUM_TCP_MESGS_PER_CONNECTION 10
#define NUM_UDP_DATAGRAMS_PER_CLIENT 5
#define TCP_SERVER_PORT 10000
#define UDP_SERVER_PORT TCP_SERVER_PORT
#define SERVER_MAX_BIND_COUNT 100

#ifdef WINCE
#  define perror(s)
#endif

static PRInt32 num_tcp_clients = NUM_TCP_CLIENTS;
static PRInt32 num_udp_clients = NUM_UDP_CLIENTS;
static PRInt32 num_transmitfile_clients = NUM_TRANSMITFILE_CLIENTS;
static PRInt32 num_tcp_connections_per_client = NUM_TCP_CONNECTIONS_PER_CLIENT;
static PRInt32 tcp_mesg_size = TCP_MESG_SIZE;
static PRInt32 num_tcp_mesgs_per_connection = NUM_TCP_MESGS_PER_CONNECTION;
static PRInt32 num_udp_datagrams_per_client = NUM_UDP_DATAGRAMS_PER_CLIENT;
static PRInt32 udp_datagram_size = UDP_DGRAM_SIZE;

static PRInt32 thread_count;
PRUint16 server_domain = PR_AF_INET, client_domain = PR_AF_INET;

/* an I/O layer that uses the emulated senfile method */
static PRDescIdentity emuSendFileIdentity;
static PRIOMethods emuSendFileMethods;

int failed_already = 0;
typedef struct buffer {
  char data[BUF_DATA_SIZE];
} buffer;

PRNetAddr tcp_server_addr, udp_server_addr;

typedef struct Serve_Client_Param {
  PRFileDesc* sockfd; /* socket to read from/write to    */
  PRInt32 datalen;    /* bytes of data transfered in each read/write */
} Serve_Client_Param;

typedef struct Server_Param {
  PRSemaphore* addr_sem; /* sem to post on, after setting up the address */
  PRMonitor* exit_mon;   /* monitor to signal on exit            */
  PRInt32* exit_counter; /* counter to decrement, before exit        */
  PRInt32 datalen;       /* bytes of data transfered in each read/write    */
} Server_Param;

typedef struct Client_Param {
  PRNetAddr server_addr;
  PRMonitor* exit_mon;   /* monitor to signal on exit */
  PRInt32* exit_counter; /* counter to decrement, before exit */
  PRInt32 datalen;
  PRInt32 udp_connect; /* if set clients connect udp sockets */
} Client_Param;

/* the sendfile method in emuSendFileMethods */
static PRInt32 PR_CALLBACK emu_SendFile(PRFileDesc* sd, PRSendFileData* sfd,
                                        PRTransmitFileFlags flags,
                                        PRIntervalTime timeout) {
  return PR_EmulateSendFile(sd, sfd, flags, timeout);
}

/* the transmitfile method in emuSendFileMethods */
static PRInt32 PR_CALLBACK emu_TransmitFile(PRFileDesc* sd, PRFileDesc* fd,
                                            const void* headers, PRInt32 hlen,
                                            PRTransmitFileFlags flags,
                                            PRIntervalTime timeout) {
  PRSendFileData sfd;

  sfd.fd = fd;
  sfd.file_offset = 0;
  sfd.file_nbytes = 0;
  sfd.header = headers;
  sfd.hlen = hlen;
  sfd.trailer = NULL;
  sfd.tlen = 0;
  return emu_SendFile(sd, &sfd, flags, timeout);
}

/*
 * readn
 *    read data from sockfd into buf
 */
static PRInt32 readn(PRFileDesc* sockfd, char* buf, int len) {
  int rem;
  int bytes;
  int offset = 0;
  int err;
  PRIntervalTime timeout = PR_INTERVAL_NO_TIMEOUT;

  if (test_cancelio) {
    timeout = PR_SecondsToInterval(2);
  }

  for (rem = len; rem; offset += bytes, rem -= bytes) {
    DPRINTF(("thread = 0x%lx: calling PR_Recv, bytes = %d\n",
             PR_GetCurrentThread(), rem));
  retry:
    bytes = PR_Recv(sockfd, buf + offset, rem, 0, timeout);
    DPRINTF(("thread = 0x%lx: returning from PR_Recv, bytes = %d\n",
             PR_GetCurrentThread(), bytes));
    if (bytes < 0) {
#ifdef WINNT
      printf("PR_Recv: error = %d oserr = %d\n", (err = PR_GetError()),
             PR_GetOSError());
      if ((test_cancelio) && (err == PR_IO_TIMEOUT_ERROR)) {
        if (PR_NT_CancelIo(sockfd) != PR_SUCCESS) {
          printf("PR_NT_CancelIO: error = %d\n", PR_GetError());
        }
        timeout = PR_INTERVAL_NO_TIMEOUT;
        goto retry;
      }
#endif
      return -1;
    }
  }
  return len;
}

/*
 * writen
 *    write data from buf to sockfd
 */
static PRInt32 writen(PRFileDesc* sockfd, char* buf, int len) {
  int rem;
  int bytes;
  int offset = 0;

  for (rem = len; rem; offset += bytes, rem -= bytes) {
    DPRINTF(("thread = 0x%lx: calling PR_Send, bytes = %d\n",
             PR_GetCurrentThread(), rem));
    bytes = PR_Send(sockfd, buf + offset, rem, 0, PR_INTERVAL_NO_TIMEOUT);
    DPRINTF(("thread = 0x%lx: returning from PR_Send, bytes = %d\n",
             PR_GetCurrentThread(), bytes));
    if (bytes <= 0) {
      return -1;
    }
  }
  return len;
}

/*
 * Serve_Client
 *    Thread, started by the server, for serving a client connection.
 *    Reads data from socket and writes it back, unmodified, and
 *    closes the socket
 */
static void PR_CALLBACK Serve_Client(void* arg) {
  Serve_Client_Param* scp = (Serve_Client_Param*)arg;
  PRFileDesc* sockfd;
  buffer* in_buf;
  PRInt32 bytes, j;

  sockfd = scp->sockfd;
  bytes = scp->datalen;
  in_buf = PR_NEW(buffer);
  if (in_buf == NULL) {
    fprintf(stderr, "prsocket_test: failed to alloc buffer struct\n");
    failed_already = 1;
    goto exit;
  }

  for (j = 0; j < num_tcp_mesgs_per_connection; j++) {
    /*
     * Read data from client and send it back to the client unmodified
     */
    if (readn(sockfd, in_buf->data, bytes) < bytes) {
      fprintf(stderr, "prsocket_test: ERROR - Serve_Client:readn\n");
      failed_already = 1;
      goto exit;
    }
    /* Shutdown only RCV will cause error on Symbian OS */
    /*
     * shutdown reads, after the last read
     */
    if (j == num_tcp_mesgs_per_connection - 1)
      if (PR_Shutdown(sockfd, PR_SHUTDOWN_RCV) < 0) {
        fprintf(stderr, "prsocket_test: ERROR - PR_Shutdown\n");
      }
    DPRINTF(("Serve_Client [0x%lx]: inbuf[0] = 0x%lx\n", PR_GetCurrentThread(),
             (*((int*)in_buf->data))));
    if (writen(sockfd, in_buf->data, bytes) < bytes) {
      fprintf(stderr, "prsocket_test: ERROR - Serve_Client:writen\n");
      failed_already = 1;
      goto exit;
    }
  }
  /*
   * shutdown reads and writes
   */
  if (PR_Shutdown(sockfd, PR_SHUTDOWN_BOTH) < 0) {
    fprintf(stderr, "prsocket_test: ERROR - PR_Shutdown\n");
    failed_already = 1;
  }

exit:
  PR_Close(sockfd);
  if (in_buf) {
    PR_DELETE(in_buf);
  }
}

PRThread* create_new_thread(PRThreadType type, void (*start)(void* arg),
                            void* arg, PRThreadPriority priority,
                            PRThreadScope scope, PRThreadState state,
                            PRUint32 stackSize, PRInt32 index) {
  PRInt32 native_thread = 0;

  PR_ASSERT(state == PR_UNJOINABLE_THREAD);
#if defined(_PR_PTHREADS) || defined(WIN32)
  switch (index % 4) {
    case 0:
      scope = (PR_LOCAL_THREAD);
      break;
    case 1:
      scope = (PR_GLOBAL_THREAD);
      break;
    case 2:
      scope = (PR_GLOBAL_BOUND_THREAD);
      break;
    case 3:
      native_thread = 1;
      break;
    default:
      PR_NOT_REACHED("Invalid scope");
      break;
  }
  if (native_thread) {
#  if defined(_PR_PTHREADS)
    pthread_t tid;
    if (!pthread_create(&tid, NULL, (void* (*)(void*))start, arg)) {
      return ((PRThread*)tid);
    } else {
      return (NULL);
    }
#  else
    HANDLE thandle;
    unsigned tid;

    thandle = (HANDLE)_beginthreadex(NULL, stackSize,
                                     (unsigned(__stdcall*)(void*))start, arg,
                                     STACK_SIZE_PARAM_IS_A_RESERVATION, &tid);
    return ((PRThread*)thandle);
#  endif
  } else {
    return (
        PR_CreateThread(type, start, arg, priority, scope, state, stackSize));
  }
#else
  return (PR_CreateThread(type, start, arg, priority, scope, state, stackSize));
#endif
}

/*
 * TCP Server
 *    Server Thread
 *    Bind an address to a socket and listen for incoming connections
 *    Start a Serve_Client thread for each incoming connection.
 */
static void PR_CALLBACK TCP_Server(void* arg) {
  PRThread* t;
  Server_Param* sp = (Server_Param*)arg;
  Serve_Client_Param* scp;
  PRFileDesc *sockfd, *newsockfd;
  PRNetAddr netaddr;
  PRInt32 i;
  /*
   * Create a tcp socket
   */
  if ((sockfd = PR_OpenTCPSocket(server_domain)) == NULL) {
    fprintf(stderr, "prsocket_test: PR_NewTCPSocket failed\n");
    goto exit;
  }
  memset(&netaddr, 0, sizeof(netaddr));

  if (PR_SetNetAddr(PR_IpAddrAny, server_domain, TCP_SERVER_PORT, &netaddr) ==
      PR_FAILURE) {
    fprintf(stderr, "prsocket_test: PR_SetNetAddr failed\n");
    goto exit;
  }
  /*
   * try a few times to bind server's address, if addresses are in
   * use
   */
  i = 0;

  while (PR_Bind(sockfd, &netaddr) < 0) {
    if (PR_GetError() == PR_ADDRESS_IN_USE_ERROR) {
      netaddr.inet.port += 2;
      if (i++ < SERVER_MAX_BIND_COUNT) {
        continue;
      }
    }
    fprintf(stderr, "prsocket_test: ERROR - PR_Bind failed\n");
    perror("PR_Bind");
    failed_already = 1;
    goto exit;
  }

  if (PR_Listen(sockfd, 32) < 0) {
    fprintf(stderr, "prsocket_test: ERROR - PR_Listen failed\n");
    failed_already = 1;
    goto exit;
  }

  if (PR_GetSockName(sockfd, &netaddr) < 0) {
    fprintf(stderr, "prsocket_test: ERROR - PR_GetSockName failed\n");
    failed_already = 1;
    goto exit;
  }

  DPRINTF(
      ("TCP_Server: PR_BIND netaddr.inet.ip = 0x%lx, netaddr.inet.port = %d\n",
       netaddr.inet.ip, netaddr.inet.port));
  if (PR_SetNetAddr(PR_IpAddrLoopback, client_domain,
                    PR_ntohs(PR_NetAddrInetPort(&netaddr)),
                    &tcp_server_addr) == PR_FAILURE) {
    fprintf(stderr, "prsocket_test: PR_SetNetAddr failed\n");
    goto exit;
  }
  if ((client_domain == PR_AF_INET6) && (server_domain == PR_AF_INET))
    PR_ConvertIPv4AddrToIPv6(PR_htonl(INADDR_LOOPBACK),
                             &tcp_server_addr.ipv6.ip);

  /*
   * Wake up parent thread because server address is bound and made
   * available in the global variable 'tcp_server_addr'
   */
  PR_PostSem(sp->addr_sem);

  for (i = 0; i < (num_tcp_clients * num_tcp_connections_per_client); i++) {
    /* test both null and non-null 'addr' argument to PR_Accept */
    PRNetAddr* addrp = (i % 2 ? &netaddr : NULL);

    DPRINTF(("TCP_Server: Accepting connection\n"));
    if ((newsockfd = PR_Accept(sockfd, addrp, PR_INTERVAL_NO_TIMEOUT)) ==
        NULL) {
      fprintf(stderr, "prsocket_test: ERROR - PR_Accept failed\n");
      goto exit;
    }
    DPRINTF(("TCP_Server: Accepted connection\n"));
    scp = PR_NEW(Serve_Client_Param);
    if (scp == NULL) {
      fprintf(stderr, "prsocket_test: PR_NEW failed\n");
      goto exit;
    }

    /*
     * Start a Serve_Client thread for each incoming connection
     */
    scp->sockfd = newsockfd;
    scp->datalen = sp->datalen;

    t = create_new_thread(PR_USER_THREAD, Serve_Client, (void*)scp,
                          PR_PRIORITY_NORMAL, PR_LOCAL_THREAD,
                          PR_UNJOINABLE_THREAD, 0, i);
    if (t == NULL) {
      fprintf(stderr, "prsocket_test: PR_CreateThread failed\n");
      failed_already = 1;
      goto exit;
    }
    DPRINTF(("TCP_Server: Created Serve_Client = 0x%lx\n", t));
  }

exit:
  if (sockfd) {
    PR_Close(sockfd);
  }

  /*
   * Decrement exit_counter and notify parent thread
   */

  PR_EnterMonitor(sp->exit_mon);
  --(*sp->exit_counter);
  PR_Notify(sp->exit_mon);
  PR_ExitMonitor(sp->exit_mon);
  DPRINTF(("TCP_Server [0x%lx] exiting\n", PR_GetCurrentThread()));
}

/*
 * UDP Server
 *    Server Thread
 *    Bind an address to a socket, read data from clients and send data
 *    back to clients
 */
static void PR_CALLBACK UDP_Server(void* arg) {
  Server_Param* sp = (Server_Param*)arg;
  PRFileDesc* sockfd;
  buffer* in_buf;
  PRNetAddr netaddr;
  PRInt32 bytes, i, rv = 0;

  bytes = sp->datalen;
  /*
   * Create a udp socket
   */
  if ((sockfd = PR_OpenUDPSocket(server_domain)) == NULL) {
    fprintf(stderr, "prsocket_test: PR_NewUDPSocket failed\n");
    failed_already = 1;
    return;
  }
  memset(&netaddr, 0, sizeof(netaddr));
  if (PR_SetNetAddr(PR_IpAddrAny, server_domain, UDP_SERVER_PORT, &netaddr) ==
      PR_FAILURE) {
    fprintf(stderr, "prsocket_test: PR_SetNetAddr failed\n");
    failed_already = 1;
    return;
  }
  /*
   * try a few times to bind server's address, if addresses are in
   * use
   */
  i = 0;
  while (PR_Bind(sockfd, &netaddr) < 0) {
    if (PR_GetError() == PR_ADDRESS_IN_USE_ERROR) {
      netaddr.inet.port += 2;
      if (i++ < SERVER_MAX_BIND_COUNT) {
        continue;
      }
    }
    fprintf(stderr, "prsocket_test: ERROR - PR_Bind failed\n");
    perror("PR_Bind");
    failed_already = 1;
    return;
  }

  if (PR_GetSockName(sockfd, &netaddr) < 0) {
    fprintf(stderr, "prsocket_test: ERROR - PR_GetSockName failed\n");
    failed_already = 1;
    return;
  }

  DPRINTF(
      ("PR_Bind: UDP Server netaddr.inet.ip = 0x%lx, netaddr.inet.port = %d\n",
       netaddr.inet.ip, netaddr.inet.port));
  /*
   * We can't use the IP address returned by PR_GetSockName in
   * netaddr.inet.ip because netaddr.inet.ip is returned
   * as 0 (= PR_INADDR_ANY).
   */

  if (PR_SetNetAddr(PR_IpAddrLoopback, client_domain,
                    PR_ntohs(PR_NetAddrInetPort(&netaddr)),
                    &udp_server_addr) == PR_FAILURE) {
    fprintf(stderr, "prsocket_test: PR_SetNetAddr failed\n");
    failed_already = 1;
    return;
  }
  if ((client_domain == PR_AF_INET6) && (server_domain == PR_AF_INET))
    PR_ConvertIPv4AddrToIPv6(PR_htonl(INADDR_LOOPBACK),
                             &udp_server_addr.ipv6.ip);

  /*
   * Wake up parent thread because server address is bound and made
   * available in the global variable 'udp_server_addr'
   */
  PR_PostSem(sp->addr_sem);

  bytes = sp->datalen;
  in_buf = PR_NEW(buffer);
  if (in_buf == NULL) {
    fprintf(stderr, "prsocket_test: failed to alloc buffer struct\n");
    failed_already = 1;
    return;
  }
  /*
   * Receive datagrams from clients and send them back, unmodified, to the
   * clients
   */
  memset(&netaddr, 0, sizeof(netaddr));
  for (i = 0; i < (num_udp_clients * num_udp_datagrams_per_client); i++) {
    DPRINTF(
        ("UDP_Server: calling PR_RecvFrom client  - ip = 0x%lx, port = %d "
         "bytes = %d inbuf = 0x%lx, inbuf[0] = 0x%lx\n",
         netaddr.inet.ip, netaddr.inet.port, bytes, in_buf->data,
         in_buf->data[0]));

    rv = PR_RecvFrom(sockfd, in_buf->data, bytes, 0, &netaddr,
                     PR_INTERVAL_NO_TIMEOUT);
    DPRINTF((
        "UDP_Server: PR_RecvFrom client  - ip = 0x%lx, port = %d bytes = %d "
        "inbuf = 0x%lx, inbuf[0] = 0x%lx\n",
        netaddr.inet.ip, netaddr.inet.port, rv, in_buf->data, in_buf->data[0]));
    if (rv != bytes) {
      return;
    }
    rv = PR_SendTo(sockfd, in_buf->data, bytes, 0, &netaddr,
                   PR_INTERVAL_NO_TIMEOUT);
    if (rv != bytes) {
      return;
    }
  }

  PR_DELETE(in_buf);
  PR_Close(sockfd);

  /*
   * Decrement exit_counter and notify parent thread
   */
  PR_EnterMonitor(sp->exit_mon);
  --(*sp->exit_counter);
  PR_Notify(sp->exit_mon);
  PR_ExitMonitor(sp->exit_mon);
  DPRINTF(("UDP_Server [0x%x] exiting\n", PR_GetCurrentThread()));
}

/*
 * TCP_Client
 *    Client Thread
 *    Connect to the server at the address specified in the argument.
 *    Fill in a buffer, write data to server, read it back and check
 *    for data corruption.
 *    Close the socket for server connection
 */
static void PR_CALLBACK TCP_Client(void* arg) {
  Client_Param* cp = (Client_Param*)arg;
  PRFileDesc* sockfd;
  buffer *in_buf, *out_buf;
  union PRNetAddr netaddr;
  PRInt32 bytes, i, j;

  bytes = cp->datalen;
  out_buf = PR_NEW(buffer);
  if (out_buf == NULL) {
    fprintf(stderr, "prsocket_test: failed to alloc buffer struct\n");
    failed_already = 1;
    return;
  }
  in_buf = PR_NEW(buffer);
  if (in_buf == NULL) {
    fprintf(stderr, "prsocket_test: failed to alloc buffer struct\n");
    failed_already = 1;
    return;
  }
  netaddr = cp->server_addr;

  for (i = 0; i < num_tcp_connections_per_client; i++) {
    if ((sockfd = PR_OpenTCPSocket(client_domain)) == NULL) {
      fprintf(stderr, "prsocket_test: PR_OpenTCPSocket failed\n");
      failed_already = 1;
      return;
    }
    if (PR_Connect(sockfd, &netaddr, PR_INTERVAL_NO_TIMEOUT) < 0) {
      fprintf(stderr, "PR_Connect failed: (%ld, %ld)\n", PR_GetError(),
              PR_GetOSError());
      failed_already = 1;
      return;
    }
    for (j = 0; j < num_tcp_mesgs_per_connection; j++) {
      /*
       * fill in random data
       */
      memset(out_buf->data, ((PRInt32)(&netaddr)) + i + j, bytes);
      /*
       * write to server
       */
#ifdef WINNT
      if (test_cancelio && (j == 0)) {
        PR_Sleep(PR_SecondsToInterval(12));
      }
#endif
      if (writen(sockfd, out_buf->data, bytes) < bytes) {
        fprintf(stderr, "prsocket_test: ERROR - TCP_Client:writen\n");
        failed_already = 1;
        return;
      }
      DPRINTF(("TCP Client [0x%lx]: out_buf = 0x%lx out_buf[0] = 0x%lx\n",
               PR_GetCurrentThread(), out_buf, (*((int*)out_buf->data))));
      if (readn(sockfd, in_buf->data, bytes) < bytes) {
        fprintf(stderr, "prsocket_test: ERROR - TCP_Client:readn\n");
        failed_already = 1;
        return;
      }
      /*
       * verify the data read
       */
      if (memcmp(in_buf->data, out_buf->data, bytes) != 0) {
        fprintf(stderr, "prsocket_test: ERROR - data corruption\n");
        failed_already = 1;
        return;
      }
    }
    /*
     * shutdown reads and writes
     */
    if (PR_Shutdown(sockfd, PR_SHUTDOWN_BOTH) < 0) {
      fprintf(stderr, "prsocket_test: ERROR - PR_Shutdown\n");
      failed_already = 1;
    }
    PR_Close(sockfd);
  }

  PR_DELETE(out_buf);
  PR_DELETE(in_buf);

  /*
   * Decrement exit_counter and notify parent thread
   */

  PR_EnterMonitor(cp->exit_mon);
  --(*cp->exit_counter);
  PR_Notify(cp->exit_mon);
  PR_ExitMonitor(cp->exit_mon);
  DPRINTF(("TCP_Client [0x%x] exiting\n", PR_GetCurrentThread()));
}

/*
 * UDP_Client
 *    Client Thread
 *    Create a socket and bind an address
 *    Communicate with the server at the address specified in the argument.
 *    Fill in a buffer, write data to server, read it back and check
 *    for data corruption.
 *    Close the socket
 */
static void PR_CALLBACK UDP_Client(void* arg) {
  Client_Param* cp = (Client_Param*)arg;
  PRFileDesc* sockfd;
  buffer *in_buf, *out_buf;
  union PRNetAddr netaddr;
  PRInt32 bytes, i, rv;

  bytes = cp->datalen;
  out_buf = PR_NEW(buffer);
  if (out_buf == NULL) {
    fprintf(stderr, "prsocket_test: failed to alloc buffer struct\n");
    failed_already = 1;
    return;
  }
  in_buf = PR_NEW(buffer);
  if (in_buf == NULL) {
    fprintf(stderr, "prsocket_test: failed to alloc buffer struct\n");
    failed_already = 1;
    return;
  }
  if ((sockfd = PR_OpenUDPSocket(client_domain)) == NULL) {
    fprintf(stderr, "prsocket_test: PR_OpenUDPSocket failed\n");
    failed_already = 1;
    return;
  }

  /*
   * bind an address for the client, let the system chose the port
   * number
   */
  memset(&netaddr, 0, sizeof(netaddr));
  if (PR_SetNetAddr(PR_IpAddrAny, client_domain, 0, &netaddr) == PR_FAILURE) {
    fprintf(stderr, "prsocket_test: PR_SetNetAddr failed\n");
    failed_already = 1;
    return;
  }
  if (PR_Bind(sockfd, &netaddr) < 0) {
    fprintf(stderr, "prsocket_test: ERROR - PR_Bind failed\n");
    perror("PR_Bind");
    return;
  }

  if (PR_GetSockName(sockfd, &netaddr) < 0) {
    fprintf(stderr, "prsocket_test: ERROR - PR_GetSockName failed\n");
    failed_already = 1;
    return;
  }

  DPRINTF(
      ("PR_Bind: UDP Client netaddr.inet.ip = 0x%lx, netaddr.inet.port = %d\n",
       netaddr.inet.ip, netaddr.inet.port));

  netaddr = cp->server_addr;

  if (cp->udp_connect) {
    if (PR_Connect(sockfd, &netaddr, PR_INTERVAL_NO_TIMEOUT) < 0) {
      fprintf(stderr, "prsocket_test: PR_Connect failed\n");
      failed_already = 1;
      return;
    }
  }

  for (i = 0; i < num_udp_datagrams_per_client; i++) {
    /*
     * fill in random data
     */
    DPRINTF(("UDP_Client [0x%lx]: out_buf = 0x%lx bytes = 0x%lx\n",
             PR_GetCurrentThread(), out_buf->data, bytes));
    memset(out_buf->data, ((PRInt32)(&netaddr)) + i, bytes);
    /*
     * write to server
     */
    if (cp->udp_connect)
      rv = PR_Send(sockfd, out_buf->data, bytes, 0, PR_INTERVAL_NO_TIMEOUT);
    else
      rv = PR_SendTo(sockfd, out_buf->data, bytes, 0, &netaddr,
                     PR_INTERVAL_NO_TIMEOUT);
    if (rv != bytes) {
      return;
    }
    DPRINTF(("UDP_Client [0x%lx]: out_buf = 0x%lx out_buf[0] = 0x%lx\n",
             PR_GetCurrentThread(), out_buf, (*((int*)out_buf->data))));
    if (cp->udp_connect)
      rv = PR_Recv(sockfd, in_buf->data, bytes, 0, PR_INTERVAL_NO_TIMEOUT);
    else
      rv = PR_RecvFrom(sockfd, in_buf->data, bytes, 0, &netaddr,
                       PR_INTERVAL_NO_TIMEOUT);
    if (rv != bytes) {
      return;
    }
    DPRINTF(("UDP_Client [0x%lx]: in_buf = 0x%lx in_buf[0] = 0x%lx\n",
             PR_GetCurrentThread(), in_buf, (*((int*)in_buf->data))));
    /*
     * verify the data read
     */
    if (memcmp(in_buf->data, out_buf->data, bytes) != 0) {
      fprintf(stderr, "prsocket_test: ERROR - UDP data corruption\n");
      failed_already = 1;
      return;
    }
  }
  PR_Close(sockfd);

  PR_DELETE(in_buf);
  PR_DELETE(out_buf);

  /*
   * Decrement exit_counter and notify parent thread
   */

  PR_EnterMonitor(cp->exit_mon);
  --(*cp->exit_counter);
  PR_Notify(cp->exit_mon);
  PR_ExitMonitor(cp->exit_mon);
  PR_DELETE(cp);
  DPRINTF(("UDP_Client [0x%x] exiting\n", PR_GetCurrentThread()));
}

/*
 * TCP_Socket_Client_Server_Test    - concurrent server test
 *
 *    One server and several clients are started
 *    Each client connects to the server and sends a chunk of data
 *    For each connection, server starts another thread to read the data
 *    from the client and send it back to the client, unmodified.
 *    Each client checks that data received from server is same as the
 *    data it sent to the server.
 *
 */

static PRInt32 TCP_Socket_Client_Server_Test(void) {
  int i;
  PRThread* t;
  PRSemaphore* server_sem;
  Server_Param* sparamp;
  Client_Param* cparamp;
  PRMonitor* mon2;
  PRInt32 datalen;

  datalen = tcp_mesg_size;
  thread_count = 0;
  /*
   * start the server thread
   */
  sparamp = PR_NEW(Server_Param);
  if (sparamp == NULL) {
    fprintf(stderr, "prsocket_test: PR_NEW failed\n");
    failed_already = 1;
    return -1;
  }
  server_sem = PR_NewSem(0);
  if (server_sem == NULL) {
    fprintf(stderr, "prsocket_test: PR_NewSem failed\n");
    failed_already = 1;
    return -1;
  }
  mon2 = PR_NewMonitor();
  if (mon2 == NULL) {
    fprintf(stderr, "prsocket_test: PR_NewMonitor failed\n");
    failed_already = 1;
    return -1;
  }
  PR_EnterMonitor(mon2);

  sparamp->addr_sem = server_sem;
  sparamp->exit_mon = mon2;
  sparamp->exit_counter = &thread_count;
  sparamp->datalen = datalen;
  t = PR_CreateThread(PR_USER_THREAD, TCP_Server, (void*)sparamp,
                      PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_UNJOINABLE_THREAD,
                      0);
  if (t == NULL) {
    fprintf(stderr, "prsocket_test: PR_CreateThread failed\n");
    failed_already = 1;
    return -1;
  }
  DPRINTF(("Created TCP server = 0x%lx\n", t));
  thread_count++;

  /*
   * wait till the server address is setup
   */
  PR_WaitSem(server_sem);

  /*
   * Now start a bunch of client threads
   */

  cparamp = PR_NEW(Client_Param);
  if (cparamp == NULL) {
    fprintf(stderr, "prsocket_test: PR_NEW failed\n");
    failed_already = 1;
    return -1;
  }
  cparamp->server_addr = tcp_server_addr;
  cparamp->exit_mon = mon2;
  cparamp->exit_counter = &thread_count;
  cparamp->datalen = datalen;
  for (i = 0; i < num_tcp_clients; i++) {
    t = create_new_thread(PR_USER_THREAD, TCP_Client, (void*)cparamp,
                          PR_PRIORITY_NORMAL, PR_LOCAL_THREAD,
                          PR_UNJOINABLE_THREAD, 0, i);
    if (t == NULL) {
      fprintf(stderr, "prsocket_test: PR_CreateThread failed\n");
      failed_already = 1;
      return -1;
    }
    DPRINTF(("Created TCP client = 0x%lx\n", t));
    thread_count++;
  }
  /* Wait for server and client threads to exit */
  while (thread_count) {
    PR_Wait(mon2, PR_INTERVAL_NO_TIMEOUT);
    DPRINTF(("TCP Server - thread_count  = %d\n", thread_count));
  }
  PR_ExitMonitor(mon2);
  printf("%30s", "TCP_Socket_Client_Server_Test:");
  printf("%2ld Server %2ld Clients %2ld connections_per_client\n", 1l,
         num_tcp_clients, num_tcp_connections_per_client);
  printf("%30s %2ld messages_per_connection %4ld bytes_per_message\n", ":",
         num_tcp_mesgs_per_connection, tcp_mesg_size);

  return 0;
}

/*
 * UDP_Socket_Client_Server_Test    - iterative server test
 *
 *    One server and several clients are started
 *    Each client connects to the server and sends a chunk of data
 *    For each connection, server starts another thread to read the data
 *    from the client and send it back to the client, unmodified.
 *    Each client checks that data received from server is same as the
 *    data it sent to the server.
 *
 */

static PRInt32 UDP_Socket_Client_Server_Test(void) {
  int i;
  PRThread* t;
  PRSemaphore* server_sem;
  Server_Param* sparamp;
  Client_Param* cparamp;
  PRMonitor* mon2;
  PRInt32 datalen;
  PRInt32 udp_connect = 1;

  datalen = udp_datagram_size;
  thread_count = 0;
  /*
   * start the server thread
   */
  sparamp = PR_NEW(Server_Param);
  if (sparamp == NULL) {
    fprintf(stderr, "prsocket_test: PR_NEW failed\n");
    failed_already = 1;
    return -1;
  }
  server_sem = PR_NewSem(0);
  if (server_sem == NULL) {
    fprintf(stderr, "prsocket_test: PR_NewSem failed\n");
    failed_already = 1;
    return -1;
  }
  mon2 = PR_NewMonitor();
  if (mon2 == NULL) {
    fprintf(stderr, "prsocket_test: PR_NewMonitor failed\n");
    failed_already = 1;
    return -1;
  }
  PR_EnterMonitor(mon2);

  sparamp->addr_sem = server_sem;
  sparamp->exit_mon = mon2;
  sparamp->exit_counter = &thread_count;
  sparamp->datalen = datalen;
  DPRINTF(("Creating UDP server"));
  t = PR_CreateThread(PR_USER_THREAD, UDP_Server, (void*)sparamp,
                      PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_UNJOINABLE_THREAD,
                      0);
  if (t == NULL) {
    fprintf(stderr, "prsocket_test: PR_CreateThread failed\n");
    failed_already = 1;
    return -1;
  }
  thread_count++;

  /*
   * wait till the server address is setup
   */
  PR_WaitSem(server_sem);

  /*
   * Now start a bunch of client threads
   */

  for (i = 0; i < num_udp_clients; i++) {
    cparamp = PR_NEW(Client_Param);
    if (cparamp == NULL) {
      fprintf(stderr, "prsocket_test: PR_NEW failed\n");
      failed_already = 1;
      return -1;
    }
    cparamp->server_addr = udp_server_addr;
    cparamp->exit_mon = mon2;
    cparamp->exit_counter = &thread_count;
    cparamp->datalen = datalen;
    /*
     * Cause every other client thread to connect udp sockets
     */
    cparamp->udp_connect = udp_connect;
    if (udp_connect) {
      udp_connect = 0;
    } else {
      udp_connect = 1;
    }
    DPRINTF(("Creating UDP client %d\n", i));
    t = PR_CreateThread(PR_USER_THREAD, UDP_Client, (void*)cparamp,
                        PR_PRIORITY_NORMAL, PR_LOCAL_THREAD,
                        PR_UNJOINABLE_THREAD, 0);
    if (t == NULL) {
      fprintf(stderr, "prsocket_test: PR_CreateThread failed\n");
      failed_already = 1;
      return -1;
    }
    thread_count++;
  }
  /* Wait for server and client threads to exit */
  while (thread_count) {
    PR_Wait(mon2, PR_INTERVAL_NO_TIMEOUT);
    DPRINTF(("UDP Server - thread_count  = %d\n", thread_count));
  }
  PR_ExitMonitor(mon2);
  printf("%30s", "UDP_Socket_Client_Server_Test: ");
  printf("%2ld Server %2ld Clients\n", 1l, num_udp_clients);
  printf("%30s %2ld datagrams_per_client %4ld bytes_per_datagram\n", ":",
         num_udp_datagrams_per_client, udp_datagram_size);

  return 0;
}

static PRFileDesc *small_file_fd, *large_file_fd;
static void *small_file_addr, *small_file_header, *large_file_addr;
static void *small_file_trailer, *large_file_header, *large_file_trailer;
/*
 * TransmitFile_Client
 *    Client Thread
 */
static void TransmitFile_Client(void* arg) {
  PRFileDesc* sockfd;
  union PRNetAddr netaddr;
  char *small_buf, *large_buf;
  Client_Param* cp = (Client_Param*)arg;
  PRInt32 rlen;

  small_buf = (char*)PR_Malloc(SMALL_FILE_SIZE + SMALL_FILE_HEADER_SIZE +
                               SMALL_FILE_TRAILER_SIZE);
  if (small_buf == NULL) {
    fprintf(stderr, "prsocket_test: failed to alloc buffer\n");
    failed_already = 1;
    return;
  }
  large_buf = (char*)PR_Malloc(LARGE_FILE_SIZE + LARGE_FILE_HEADER_SIZE +
                               LARGE_FILE_TRAILER_SIZE);
  if (large_buf == NULL) {
    fprintf(stderr, "prsocket_test: failed to alloc buffer\n");
    failed_already = 1;
    return;
  }
  netaddr.inet.family = cp->server_addr.inet.family;
  netaddr.inet.port = cp->server_addr.inet.port;
  netaddr.inet.ip = cp->server_addr.inet.ip;

  if ((sockfd = PR_NewTCPSocket()) == NULL) {
    fprintf(stderr, "prsocket_test: PR_NewTCPSocket failed\n");
    failed_already = 1;
    return;
  }

  if (PR_Connect(sockfd, &netaddr, PR_INTERVAL_NO_TIMEOUT) < 0) {
    fprintf(stderr, "prsocket_test: PR_Connect failed\n");
    failed_already = 1;
    return;
  }
  /*
   * read the small file and verify the data
   */
  if (readn(sockfd, small_buf, SMALL_FILE_SIZE + SMALL_FILE_HEADER_SIZE) !=
      (SMALL_FILE_SIZE + SMALL_FILE_HEADER_SIZE)) {
    fprintf(stderr,
            "prsocket_test: TransmitFile_Client failed to receive file\n");
    failed_already = 1;
    return;
  }
#if defined(XP_UNIX)
  /* File transmission test can not be done because of large file's size */
  if (memcmp(small_file_header, small_buf, SMALL_FILE_HEADER_SIZE) != 0) {
    fprintf(stderr,
            "prsocket_test: TransmitFile_Client ERROR - small file header data "
            "corruption\n");
    failed_already = 1;
    return;
  }
  if (memcmp(small_file_addr, small_buf + SMALL_FILE_HEADER_SIZE,
             SMALL_FILE_SIZE) != 0) {
    fprintf(stderr,
            "prsocket_test: TransmitFile_Client ERROR - small file data "
            "corruption\n");
    failed_already = 1;
    return;
  }
#endif
  /*
   * read the large file and verify the data
   */
  if (readn(sockfd, large_buf, LARGE_FILE_SIZE) != LARGE_FILE_SIZE) {
    fprintf(stderr,
            "prsocket_test: TransmitFile_Client failed to receive file\n");
    failed_already = 1;
    return;
  }
#if defined(XP_UNIX)
  if (memcmp(large_file_addr, large_buf, LARGE_FILE_SIZE) != 0) {
    fprintf(stderr,
            "prsocket_test: TransmitFile_Client ERROR - large file data "
            "corruption\n");
    failed_already = 1;
  }
#endif

  /*
   * receive data from PR_SendFile
   */
  /*
   * case 1: small file with header and trailer
   */
  rlen = SMALL_FILE_SIZE + SMALL_FILE_HEADER_SIZE + SMALL_FILE_TRAILER_SIZE;
  if (readn(sockfd, small_buf, rlen) != rlen) {
    fprintf(stderr, "prsocket_test: SendFile_Client failed to receive file\n");
    failed_already = 1;
    return;
  }
#if defined(XP_UNIX)
  if (memcmp(small_file_header, small_buf, SMALL_FILE_HEADER_SIZE) != 0) {
    fprintf(stderr, "SendFile 1. ERROR - small file header corruption\n");
    failed_already = 1;
    return;
  }
  if (memcmp(small_file_addr, small_buf + SMALL_FILE_HEADER_SIZE,
             SMALL_FILE_SIZE) != 0) {
    fprintf(stderr, "SendFile 1. ERROR - small file data corruption\n");
    failed_already = 1;
    return;
  }
  if (memcmp(small_file_trailer,
             small_buf + SMALL_FILE_HEADER_SIZE + SMALL_FILE_SIZE,
             SMALL_FILE_TRAILER_SIZE) != 0) {
    fprintf(stderr, "SendFile 1. ERROR - small file trailer corruption\n");
    failed_already = 1;
    return;
  }
#endif
  /*
   * case 2: partial large file at zero offset, file with header and trailer
   */
  rlen = LARGE_FILE_LEN_1 + LARGE_FILE_HEADER_SIZE + LARGE_FILE_TRAILER_SIZE;
  if (readn(sockfd, large_buf, rlen) != rlen) {
    fprintf(stderr, "prsocket_test: SendFile_Client failed to receive file\n");
    failed_already = 1;
    return;
  }
#if defined(XP_UNIX)
  if (memcmp(large_file_header, large_buf, LARGE_FILE_HEADER_SIZE) != 0) {
    fprintf(stderr, "SendFile 2. ERROR - large file header corruption\n");
    failed_already = 1;
    return;
  }
  if (memcmp(large_file_addr, large_buf + LARGE_FILE_HEADER_SIZE,
             LARGE_FILE_LEN_1) != 0) {
    fprintf(stderr, "SendFile 2. ERROR - large file data corruption\n");
    failed_already = 1;
    return;
  }
  if (memcmp(large_file_trailer,
             large_buf + LARGE_FILE_HEADER_SIZE + LARGE_FILE_LEN_1,
             LARGE_FILE_TRAILER_SIZE) != 0) {
    fprintf(stderr, "SendFile 2. ERROR - large file trailer corruption\n");
    failed_already = 1;
    return;
  }
#endif
  /*
   * case 3: partial small file at non-zero offset, with header
   */
  rlen = SMALL_FILE_LEN_1 + SMALL_FILE_HEADER_SIZE;
  if (readn(sockfd, small_buf, rlen) != rlen) {
    fprintf(stderr, "prsocket_test: SendFile_Client failed to receive file\n");
    failed_already = 1;
    return;
  }
#if defined(XP_UNIX)
  if (memcmp(small_file_header, small_buf, SMALL_FILE_HEADER_SIZE) != 0) {
    fprintf(stderr, "SendFile 3. ERROR - small file header corruption\n");
    failed_already = 1;
    return;
  }
  if (memcmp((char*)small_file_addr + SMALL_FILE_OFFSET_1,
             small_buf + SMALL_FILE_HEADER_SIZE, SMALL_FILE_LEN_1) != 0) {
    fprintf(stderr, "SendFile 3. ERROR - small file data corruption\n");
    failed_already = 1;
    return;
  }
#endif
  /*
   * case 4: partial small file at non-zero offset, with trailer
   */
  rlen = SMALL_FILE_LEN_2 + SMALL_FILE_TRAILER_SIZE;
  if (readn(sockfd, small_buf, rlen) != rlen) {
    fprintf(stderr, "prsocket_test: SendFile_Client failed to receive file\n");
    failed_already = 1;
    return;
  }
#if defined(XP_UNIX)
  if (memcmp((char*)small_file_addr + SMALL_FILE_OFFSET_2, small_buf,
             SMALL_FILE_LEN_2) != 0) {
    fprintf(stderr, "SendFile 4. ERROR - small file data corruption\n");
    failed_already = 1;
    return;
  }
  if (memcmp(small_file_trailer, small_buf + SMALL_FILE_LEN_2,
             SMALL_FILE_TRAILER_SIZE) != 0) {
    fprintf(stderr, "SendFile 4. ERROR - small file trailer corruption\n");
    failed_already = 1;
    return;
  }
#endif
  /*
   * case 5: partial large file at non-zero offset, file with header
   */
  rlen = LARGE_FILE_LEN_2 + LARGE_FILE_HEADER_SIZE;
  if (readn(sockfd, large_buf, rlen) != rlen) {
    fprintf(stderr, "prsocket_test: SendFile_Client failed to receive file\n");
    failed_already = 1;
    return;
  }
#if defined(XP_UNIX)
  if (memcmp(large_file_header, large_buf, LARGE_FILE_HEADER_SIZE) != 0) {
    fprintf(stderr, "SendFile 5. ERROR - large file header corruption\n");
    failed_already = 1;
    return;
  }
  if (memcmp((char*)large_file_addr + LARGE_FILE_OFFSET_2,
             large_buf + LARGE_FILE_HEADER_SIZE, LARGE_FILE_LEN_2) != 0) {
    fprintf(stderr, "SendFile 5. ERROR - large file data corruption\n");
    failed_already = 1;
    return;
  }
#endif
  /*
   * case 6: partial small file at non-zero offset, with header
   */
  rlen = SMALL_FILE_LEN_3 + SMALL_FILE_HEADER_SIZE;
  if (readn(sockfd, small_buf, rlen) != rlen) {
    fprintf(stderr, "prsocket_test: SendFile_Client failed to receive file\n");
    failed_already = 1;
    return;
  }
#if defined(XP_UNIX)
  if (memcmp(small_file_header, small_buf, SMALL_FILE_HEADER_SIZE) != 0) {
    fprintf(stderr, "SendFile 6. ERROR - small file header corruption\n");
    return;
  }
  if (memcmp((char*)small_file_addr + SMALL_FILE_OFFSET_3,
             small_buf + SMALL_FILE_HEADER_SIZE, SMALL_FILE_LEN_3) != 0) {
#  if 0
        char *i, *j;
        int k;

        i = (char *) small_file_addr + SMALL_FILE_OFFSET_3;
        j = small_buf + SMALL_FILE_HEADER_SIZE;
        k = SMALL_FILE_LEN_3;
        while (k-- > 0) {
            if (*i++ != *j++)
                printf("i = %d j = %d\n",
                       (int) (i - ((char *) small_file_addr + SMALL_FILE_OFFSET_3)),
                       (int) (j - (small_buf + SMALL_FILE_HEADER_SIZE)));
        }
#  endif
    fprintf(stderr, "SendFile 6. ERROR - small file data corruption\n");
    failed_already = 1;
    return;
  }
#endif
  /*
   * case 7: partial large file at non-zero offset, with header
   */
  rlen = LARGE_FILE_LEN_3 + LARGE_FILE_HEADER_SIZE;
  if (readn(sockfd, large_buf, rlen) != rlen) {
    fprintf(stderr, "prsocket_test: SendFile_Client failed to receive file\n");
    failed_already = 1;
    return;
  }
#if defined(XP_UNIX)
  if (memcmp(large_file_header, large_buf, LARGE_FILE_HEADER_SIZE) != 0) {
    fprintf(stderr, "SendFile 7. ERROR - large file header corruption\n");
    failed_already = 1;
    return;
  }
  if (memcmp((char*)large_file_addr + LARGE_FILE_OFFSET_3,
             large_buf + LARGE_FILE_HEADER_SIZE, LARGE_FILE_LEN_3) != 0) {
    fprintf(stderr, "SendFile 7. ERROR - large file data corruption\n");
    failed_already = 1;
    return;
  }
#endif
  /*
   * case 8: partial large file at non-zero, page-aligned offset, with
   * header and trailer
   */
  rlen = LARGE_FILE_LEN_4 + LARGE_FILE_HEADER_SIZE + LARGE_FILE_TRAILER_SIZE;
  if (readn(sockfd, large_buf, rlen) != rlen) {
    fprintf(stderr, "prsocket_test: SendFile_Client failed to receive file\n");
    failed_already = 1;
    return;
  }
#if defined(XP_UNIX)
  if (memcmp(large_file_header, large_buf, LARGE_FILE_HEADER_SIZE) != 0) {
    fprintf(stderr, "SendFile 2. ERROR - large file header corruption\n");
    failed_already = 1;
    return;
  }
  if (memcmp((char*)large_file_addr + LARGE_FILE_OFFSET_4,
             large_buf + LARGE_FILE_HEADER_SIZE, LARGE_FILE_LEN_4) != 0) {
    fprintf(stderr, "SendFile 2. ERROR - large file data corruption\n");
    failed_already = 1;
    return;
  }
  if (memcmp(large_file_trailer,
             large_buf + LARGE_FILE_HEADER_SIZE + LARGE_FILE_LEN_4,
             LARGE_FILE_TRAILER_SIZE) != 0) {
    fprintf(stderr, "SendFile 2. ERROR - large file trailer corruption\n");
    failed_already = 1;
    return;
  }
#endif
  PR_DELETE(small_buf);
  PR_DELETE(large_buf);
  PR_Close(sockfd);

  /*
   * Decrement exit_counter and notify parent thread
   */

  PR_EnterMonitor(cp->exit_mon);
  --(*cp->exit_counter);
  PR_Notify(cp->exit_mon);
  PR_ExitMonitor(cp->exit_mon);
  DPRINTF(("TransmitFile_Client [0x%lx] exiting\n", PR_GetCurrentThread()));
}

/*
 * Serve_TransmitFile_Client
 *    Thread, started by the server, for serving a client connection.
 *    Trasmits a small file, with a header, and a large file, without
 *    a header
 */
static void Serve_TransmitFile_Client(void* arg) {
  Serve_Client_Param* scp = (Serve_Client_Param*)arg;
  PRFileDesc* sockfd;
  PRInt32 bytes;
  PRFileDesc* local_small_file_fd = NULL;
  PRFileDesc* local_large_file_fd = NULL;
  PRSendFileData sfd;
  PRInt32 slen;

  sockfd = scp->sockfd;
  local_small_file_fd = PR_Open(SMALL_FILE_NAME, PR_RDONLY, 0);

  if (local_small_file_fd == NULL) {
    fprintf(stderr, "prsocket_test failed to open file for transmitting %s\n",
            SMALL_FILE_NAME);
    failed_already = 1;
    goto done;
  }
  local_large_file_fd = PR_Open(LARGE_FILE_NAME, PR_RDONLY, 0);

  if (local_large_file_fd == NULL) {
    fprintf(stderr, "prsocket_test failed to open file for transmitting %s\n",
            LARGE_FILE_NAME);
    failed_already = 1;
    goto done;
  }
  bytes = PR_TransmitFile(sockfd, local_small_file_fd, small_file_header,
                          SMALL_FILE_HEADER_SIZE, PR_TRANSMITFILE_KEEP_OPEN,
                          PR_INTERVAL_NO_TIMEOUT);
  if (bytes != (SMALL_FILE_SIZE + SMALL_FILE_HEADER_SIZE)) {
    fprintf(stderr, "prsocet_test: PR_TransmitFile failed: (%ld, %ld)\n",
            PR_GetError(), PR_GetOSError());
    failed_already = 1;
  }
  bytes = PR_TransmitFile(sockfd, local_large_file_fd, NULL, 0,
                          PR_TRANSMITFILE_KEEP_OPEN, PR_INTERVAL_NO_TIMEOUT);
  if (bytes != LARGE_FILE_SIZE) {
    fprintf(stderr, "prsocket_test: PR_TransmitFile failed: (%ld, %ld)\n",
            PR_GetError(), PR_GetOSError());
    failed_already = 1;
  }

  /*
   * PR_SendFile test cases
   */

  /*
   * case 1: small file with header and trailer
   */
  sfd.fd = local_small_file_fd;
  sfd.file_offset = 0;
  sfd.file_nbytes = 0;
  sfd.header = small_file_header;
  sfd.hlen = SMALL_FILE_HEADER_SIZE;
  sfd.trailer = small_file_trailer;
  sfd.tlen = SMALL_FILE_TRAILER_SIZE;
  bytes = PR_SendFile(sockfd, &sfd, PR_TRANSMITFILE_KEEP_OPEN,
                      PR_INTERVAL_NO_TIMEOUT);
  slen = SMALL_FILE_SIZE + SMALL_FILE_HEADER_SIZE + SMALL_FILE_TRAILER_SIZE;
  if (bytes != slen) {
    fprintf(stderr,
            "socket: Error - 1. PR_SendFile  send_size = %d, bytes sent = %d\n",
            slen, bytes);
    fprintf(stderr, "prsocket_test: PR_SendFile failed: (%ld, %ld)\n",
            PR_GetError(), PR_GetOSError());
    failed_already = 1;
  }

  /*
   * case 2: partial large file at zero offset, file with header and trailer
   */
  sfd.fd = local_large_file_fd;
  sfd.file_offset = 0;
  sfd.file_nbytes = LARGE_FILE_LEN_1;
  sfd.header = large_file_header;
  sfd.hlen = LARGE_FILE_HEADER_SIZE;
  sfd.trailer = large_file_trailer;
  sfd.tlen = LARGE_FILE_TRAILER_SIZE;
  bytes = PR_SendFile(sockfd, &sfd, PR_TRANSMITFILE_KEEP_OPEN,
                      PR_INTERVAL_NO_TIMEOUT);
  slen = LARGE_FILE_LEN_1 + LARGE_FILE_HEADER_SIZE + LARGE_FILE_TRAILER_SIZE;
  if (bytes != slen) {
    fprintf(stderr,
            "socket: Error - 2. PR_SendFile send_size = %d, bytes sent = %d\n",
            slen, bytes);
    fprintf(stderr, "prsocket_test: PR_SendFile failed: (%ld, %ld)\n",
            PR_GetError(), PR_GetOSError());
    failed_already = 1;
  }
  /*
   * case 3: partial small file at non-zero offset, with header
   */
  sfd.fd = local_small_file_fd;
  sfd.file_offset = SMALL_FILE_OFFSET_1;
  sfd.file_nbytes = SMALL_FILE_LEN_1;
  sfd.header = small_file_header;
  sfd.hlen = SMALL_FILE_HEADER_SIZE;
  sfd.trailer = NULL;
  sfd.tlen = 0;
  bytes = PR_SendFile(sockfd, &sfd, PR_TRANSMITFILE_KEEP_OPEN,
                      PR_INTERVAL_NO_TIMEOUT);
  slen = SMALL_FILE_LEN_1 + SMALL_FILE_HEADER_SIZE;
  if (bytes != slen) {
    fprintf(stderr,
            "socket: Error - 3. PR_SendFile send_size = %d, bytes sent = %d\n",
            slen, bytes);
    fprintf(stderr, "prsocket_test: PR_SendFile failed: (%ld, %ld)\n",
            PR_GetError(), PR_GetOSError());
    failed_already = 1;
  }
  /*
   * case 4: partial small file at non-zero offset, with trailer
   */
  sfd.fd = local_small_file_fd;
  sfd.file_offset = SMALL_FILE_OFFSET_2;
  sfd.file_nbytes = SMALL_FILE_LEN_2;
  sfd.header = NULL;
  sfd.hlen = 0;
  sfd.trailer = small_file_trailer;
  sfd.tlen = SMALL_FILE_TRAILER_SIZE;
  bytes = PR_SendFile(sockfd, &sfd, PR_TRANSMITFILE_KEEP_OPEN,
                      PR_INTERVAL_NO_TIMEOUT);
  slen = SMALL_FILE_LEN_2 + SMALL_FILE_TRAILER_SIZE;
  if (bytes != slen) {
    fprintf(stderr,
            "socket: Error - 4. PR_SendFile send_size = %d, bytes sent = %d\n",
            slen, bytes);
    fprintf(stderr, "prsocket_test: PR_SendFile failed: (%ld, %ld)\n",
            PR_GetError(), PR_GetOSError());
    failed_already = 1;
  }
  /*
   * case 5: partial large file at non-zero offset, file with header
   */
  sfd.fd = local_large_file_fd;
  sfd.file_offset = LARGE_FILE_OFFSET_2;
  sfd.file_nbytes = LARGE_FILE_LEN_2;
  sfd.header = large_file_header;
  sfd.hlen = LARGE_FILE_HEADER_SIZE;
  sfd.trailer = NULL;
  sfd.tlen = 0;
  bytes = PR_SendFile(sockfd, &sfd, PR_TRANSMITFILE_KEEP_OPEN,
                      PR_INTERVAL_NO_TIMEOUT);
  slen = LARGE_FILE_LEN_2 + LARGE_FILE_HEADER_SIZE;
  if (bytes != slen) {
    fprintf(stderr,
            "socket: Error - 5. PR_SendFile send_size = %d, bytes sent = %d\n",
            slen, bytes);
    fprintf(stderr, "prsocket_test: PR_SendFile failed: (%ld, %ld)\n",
            PR_GetError(), PR_GetOSError());
    failed_already = 1;
  }
  /*
   * case 6: partial small file from non-zero offset till end of file, with
   * header
   */
  sfd.fd = local_small_file_fd;
  sfd.file_offset = SMALL_FILE_OFFSET_3;
  sfd.file_nbytes = 0; /* data from offset to end-of-file */
  sfd.header = small_file_header;
  sfd.hlen = SMALL_FILE_HEADER_SIZE;
  sfd.trailer = NULL;
  sfd.tlen = 0;
  bytes = PR_SendFile(sockfd, &sfd, PR_TRANSMITFILE_KEEP_OPEN,
                      PR_INTERVAL_NO_TIMEOUT);
  slen = SMALL_FILE_LEN_3 + SMALL_FILE_HEADER_SIZE;
  if (bytes != slen) {
    fprintf(stderr,
            "socket: Error - 6. PR_SendFile send_size = %d, bytes sent = %d\n",
            slen, bytes);
    fprintf(stderr, "prsocket_test: PR_SendFile failed: (%ld, %ld)\n",
            PR_GetError(), PR_GetOSError());
    failed_already = 1;
  }
  /*
   * case 7: partial large file at non-zero offset till end-of-file, with header
   */
  sfd.fd = local_large_file_fd;
  sfd.file_offset = LARGE_FILE_OFFSET_3;
  sfd.file_nbytes = 0; /* data until end-of-file */
  sfd.header = large_file_header;
  sfd.hlen = LARGE_FILE_HEADER_SIZE;
  sfd.trailer = NULL;
  sfd.tlen = 0;
  bytes = PR_SendFile(sockfd, &sfd, PR_TRANSMITFILE_KEEP_OPEN,
                      PR_INTERVAL_NO_TIMEOUT);
  slen = LARGE_FILE_LEN_3 + LARGE_FILE_HEADER_SIZE;
  if (bytes != slen) {
    fprintf(stderr,
            "socket: Error - 7. PR_SendFile send_size = %d, bytes sent = %d\n",
            slen, bytes);
    fprintf(stderr, "prsocket_test: PR_SendFile failed: (%ld, %ld)\n",
            PR_GetError(), PR_GetOSError());
    failed_already = 1;
  }
  /*
   * case 8: partial large file at non-zero page-aligned offset,
   * with header and trailer
   */
  sfd.fd = local_large_file_fd;
  sfd.file_offset = LARGE_FILE_OFFSET_4;
  sfd.file_nbytes = LARGE_FILE_LEN_4;
  sfd.header = large_file_header;
  sfd.hlen = LARGE_FILE_HEADER_SIZE;
  sfd.trailer = large_file_trailer;
  sfd.tlen = LARGE_FILE_TRAILER_SIZE;
  bytes = PR_SendFile(sockfd, &sfd, PR_TRANSMITFILE_CLOSE_SOCKET,
                      PR_INTERVAL_NO_TIMEOUT);
  slen = LARGE_FILE_LEN_4 + LARGE_FILE_HEADER_SIZE + LARGE_FILE_TRAILER_SIZE;
  if (bytes != slen) {
    fprintf(stderr,
            "socket: Error - 2. PR_SendFile send_size = %d, bytes sent = %d\n",
            slen, bytes);
    fprintf(stderr, "prsocket_test: PR_SendFile failed: (%ld, %ld)\n",
            PR_GetError(), PR_GetOSError());
    failed_already = 1;
  }
done:
  if (local_small_file_fd != NULL) {
    PR_Close(local_small_file_fd);
  }
  if (local_large_file_fd != NULL) {
    PR_Close(local_large_file_fd);
  }
}

/*
 * TransmitFile Server
 *    Server Thread
 *    Bind an address to a socket and listen for incoming connections
 *    Create worker threads to service clients
 */
static void TransmitFile_Server(void* arg) {
  PRThread** t = NULL; /* an array of PRThread pointers */
  Server_Param* sp = (Server_Param*)arg;
  Serve_Client_Param* scp;
  PRFileDesc *sockfd = NULL, *newsockfd;
  PRNetAddr netaddr;
  PRInt32 i;

  t = (PRThread**)PR_MALLOC(num_transmitfile_clients * sizeof(PRThread*));
  if (t == NULL) {
    fprintf(stderr, "prsocket_test: run out of memory\n");
    failed_already = 1;
    goto exit;
  }
  /*
   * Create a tcp socket
   */
  if ((sockfd = PR_OpenTCPSocket(PR_AF_INET)) == NULL) {
    fprintf(stderr, "prsocket_test: PR_OpenTCPSocket failed\n");
    failed_already = 1;
    goto exit;
  }
  memset(&netaddr, 0, sizeof(netaddr));
  netaddr.inet.family = PR_AF_INET;
  netaddr.inet.port = PR_htons(TCP_SERVER_PORT);
  netaddr.inet.ip = PR_htonl(PR_INADDR_ANY);
  /*
   * try a few times to bind server's address, if addresses are in
   * use
   */
  i = 0;
  while (PR_Bind(sockfd, &netaddr) < 0) {
    if (PR_GetError() == PR_ADDRESS_IN_USE_ERROR) {
      netaddr.inet.port += 2;
      if (i++ < SERVER_MAX_BIND_COUNT) {
        continue;
      }
    }
    fprintf(stderr, "prsocket_test: ERROR - PR_Bind failed\n");
    failed_already = 1;
    perror("PR_Bind");
    goto exit;
  }

  if (PR_Listen(sockfd, 32) < 0) {
    fprintf(stderr, "prsocket_test: ERROR - PR_Listen failed\n");
    failed_already = 1;
    goto exit;
  }

  if (PR_GetSockName(sockfd, &netaddr) < 0) {
    fprintf(stderr, "prsocket_test: ERROR - PR_GetSockName failed\n");
    failed_already = 1;
    goto exit;
  }

  DPRINTF(
      ("TCP_Server: PR_BIND netaddr.inet.ip = 0x%lx, netaddr.inet.port = %d\n",
       netaddr.inet.ip, netaddr.inet.port));
  tcp_server_addr.inet.family = netaddr.inet.family;
  tcp_server_addr.inet.port = netaddr.inet.port;
  tcp_server_addr.inet.ip = netaddr.inet.ip;

  /*
   * Wake up parent thread because server address is bound and made
   * available in the global variable 'tcp_server_addr'
   */
  PR_PostSem(sp->addr_sem);

  for (i = 0; i < num_transmitfile_clients; i++) {
    /* test both null and non-null 'addr' argument to PR_Accept */
    PRNetAddr* addrp = (i % 2 ? &netaddr : NULL);

    if ((newsockfd = PR_Accept(sockfd, addrp, PR_INTERVAL_NO_TIMEOUT)) ==
        NULL) {
      fprintf(stderr, "prsocket_test: ERROR - PR_Accept failed\n");
      failed_already = 1;
      goto exit;
    }
    /* test both regular and emulated PR_SendFile */
    if (i % 2) {
      PRFileDesc* layer =
          PR_CreateIOLayerStub(emuSendFileIdentity, &emuSendFileMethods);
      if (layer == NULL) {
        fprintf(stderr, "prsocket_test: ERROR - PR_CreateIOLayerStub failed\n");
        failed_already = 1;
        goto exit;
      }
      if (PR_PushIOLayer(newsockfd, PR_TOP_IO_LAYER, layer) == PR_FAILURE) {
        fprintf(stderr, "prsocket_test: ERROR - PR_PushIOLayer failed\n");
        failed_already = 1;
        goto exit;
      }
    }
    scp = PR_NEW(Serve_Client_Param);
    if (scp == NULL) {
      fprintf(stderr, "prsocket_test: PR_NEW failed\n");
      failed_already = 1;
      goto exit;
    }

    /*
     * Start a Serve_Client thread for each incoming connection
     */
    scp->sockfd = newsockfd;
    scp->datalen = sp->datalen;

    t[i] = PR_CreateThread(PR_USER_THREAD, Serve_TransmitFile_Client,
                           (void*)scp, PR_PRIORITY_NORMAL, PR_LOCAL_THREAD,
                           PR_JOINABLE_THREAD, 0);
    if (t[i] == NULL) {
      fprintf(stderr, "prsocket_test: PR_CreateThread failed\n");
      failed_already = 1;
      goto exit;
    }
    DPRINTF(("TransmitFile_Server: Created Serve_TransmitFile_Client = 0x%lx\n",
             t));
  }

  /*
   * Wait for all the worker threads to end, so that we know
   * they are no longer using the small and large file fd's.
   */

  for (i = 0; i < num_transmitfile_clients; i++) {
    PR_JoinThread(t[i]);
  }

exit:
  if (t) {
    PR_DELETE(t);
  }
  if (sockfd) {
    PR_Close(sockfd);
  }

  /*
   * Decrement exit_counter and notify parent thread
   */

  PR_EnterMonitor(sp->exit_mon);
  --(*sp->exit_counter);
  PR_Notify(sp->exit_mon);
  PR_ExitMonitor(sp->exit_mon);
  DPRINTF(("TransmitFile_Server [0x%lx] exiting\n", PR_GetCurrentThread()));
}

/*
 * Socket_Misc_Test    - test miscellaneous functions
 *
 */
static PRInt32 Socket_Misc_Test(void) {
  PRIntn i, rv = 0, bytes, count, len;
  PRThread* t;
  PRSemaphore* server_sem;
  Server_Param* sparamp;
  Client_Param* cparamp;
  PRMonitor* mon2;
  PRInt32 datalen;

  /*
   * We deliberately pick a buffer size that is not a nice multiple
   * of 1024.
   */
#define TRANSMITFILE_BUF_SIZE (4 * 1024 - 11)

  typedef struct {
    char data[TRANSMITFILE_BUF_SIZE];
  } file_buf;
  file_buf* buf = NULL;

  /*
   * create file(s) to be transmitted
   */
  if ((PR_MkDir(TEST_DIR, 0777)) < 0) {
    printf("prsocket_test failed to create dir %s\n", TEST_DIR);
    failed_already = 1;
    return -1;
  }

  small_file_fd = PR_Open(SMALL_FILE_NAME, PR_RDWR | PR_CREATE_FILE, 0777);

  if (small_file_fd == NULL) {
    fprintf(stderr, "prsocket_test failed to create/open file %s\n",
            SMALL_FILE_NAME);
    failed_already = 1;
    rv = -1;
    goto done;
  }
  buf = PR_NEW(file_buf);
  if (buf == NULL) {
    fprintf(stderr, "prsocket_test failed to allocate buffer\n");
    failed_already = 1;
    rv = -1;
    goto done;
  }
  /*
   * fill in random data
   */
  for (i = 0; i < TRANSMITFILE_BUF_SIZE; i++) {
    buf->data[i] = i;
  }
  count = 0;
  do {
    len = (SMALL_FILE_SIZE - count) > TRANSMITFILE_BUF_SIZE
              ? TRANSMITFILE_BUF_SIZE
              : (SMALL_FILE_SIZE - count);
    bytes = PR_Write(small_file_fd, buf->data, len);
    if (bytes <= 0) {
      fprintf(stderr, "prsocket_test failed to write to file %s\n",
              SMALL_FILE_NAME);
      failed_already = 1;
      rv = -1;
      goto done;
    }
    count += bytes;
  } while (count < SMALL_FILE_SIZE);
#ifdef XP_UNIX
  /*
   * map the small file; used in checking for data corruption
   */
  small_file_addr = mmap(0, SMALL_FILE_SIZE, PROT_READ, MAP_SHARED,
                         small_file_fd->secret->md.osfd, 0);
  if (small_file_addr == (void*)-1) {
    fprintf(stderr, "prsocket_test failed to mmap file %s\n", SMALL_FILE_NAME);
    failed_already = 1;
    rv = -1;
    goto done;
  }
#endif
  /*
   * header for small file
   */
  small_file_header = PR_MALLOC(SMALL_FILE_HEADER_SIZE);
  if (small_file_header == NULL) {
    fprintf(stderr, "prsocket_test failed to malloc header file\n");
    failed_already = 1;
    rv = -1;
    goto done;
  }
  memset(small_file_header, (int)PR_IntervalNow(), SMALL_FILE_HEADER_SIZE);
  /*
   * trailer for small file
   */
  small_file_trailer = PR_MALLOC(SMALL_FILE_TRAILER_SIZE);
  if (small_file_trailer == NULL) {
    fprintf(stderr, "prsocket_test failed to malloc header trailer\n");
    failed_already = 1;
    rv = -1;
    goto done;
  }
  memset(small_file_trailer, (int)PR_IntervalNow(), SMALL_FILE_TRAILER_SIZE);
  /*
   * setup large file
   */
  large_file_fd = PR_Open(LARGE_FILE_NAME, PR_RDWR | PR_CREATE_FILE, 0777);

  if (large_file_fd == NULL) {
    fprintf(stderr, "prsocket_test failed to create/open file %s\n",
            LARGE_FILE_NAME);
    failed_already = 1;
    rv = -1;
    goto done;
  }
  /*
   * fill in random data
   */
  for (i = 0; i < TRANSMITFILE_BUF_SIZE; i++) {
    buf->data[i] = i;
  }
  count = 0;
  do {
    len = (LARGE_FILE_SIZE - count) > TRANSMITFILE_BUF_SIZE
              ? TRANSMITFILE_BUF_SIZE
              : (LARGE_FILE_SIZE - count);
    bytes = PR_Write(large_file_fd, buf->data, len);
    if (bytes <= 0) {
      fprintf(stderr, "prsocket_test failed to write to file %s: (%ld, %ld)\n",
              LARGE_FILE_NAME, PR_GetError(), PR_GetOSError());
      failed_already = 1;
      rv = -1;
      goto done;
    }
    count += bytes;
  } while (count < LARGE_FILE_SIZE);
#if defined(XP_UNIX)
  /*
   * map the large file; used in checking for data corruption
   */
  large_file_addr = mmap(0, LARGE_FILE_SIZE, PROT_READ, MAP_SHARED,
                         large_file_fd->secret->md.osfd, 0);
  if (large_file_addr == (void*)-1) {
    fprintf(stderr, "prsocket_test failed to mmap file %s\n", LARGE_FILE_NAME);
    failed_already = 1;
    rv = -1;
    goto done;
  }
#endif
  /*
   * header for large file
   */
  large_file_header = PR_MALLOC(LARGE_FILE_HEADER_SIZE);
  if (large_file_header == NULL) {
    fprintf(stderr, "prsocket_test failed to malloc header file\n");
    failed_already = 1;
    rv = -1;
    goto done;
  }
  memset(large_file_header, (int)PR_IntervalNow(), LARGE_FILE_HEADER_SIZE);
  /*
   * trailer for large file
   */
  large_file_trailer = PR_MALLOC(LARGE_FILE_TRAILER_SIZE);
  if (large_file_trailer == NULL) {
    fprintf(stderr, "prsocket_test failed to malloc header trailer\n");
    failed_already = 1;
    rv = -1;
    goto done;
  }
  memset(large_file_trailer, (int)PR_IntervalNow(), LARGE_FILE_TRAILER_SIZE);

  datalen = tcp_mesg_size;
  thread_count = 0;
  /*
   * start the server thread
   */
  sparamp = PR_NEW(Server_Param);
  if (sparamp == NULL) {
    fprintf(stderr, "prsocket_test: PR_NEW failed\n");
    failed_already = 1;
    rv = -1;
    goto done;
  }
  server_sem = PR_NewSem(0);
  if (server_sem == NULL) {
    fprintf(stderr, "prsocket_test: PR_NewSem failed\n");
    failed_already = 1;
    rv = -1;
    goto done;
  }
  mon2 = PR_NewMonitor();
  if (mon2 == NULL) {
    fprintf(stderr, "prsocket_test: PR_NewMonitor failed\n");
    failed_already = 1;
    rv = -1;
    goto done;
  }
  PR_EnterMonitor(mon2);

  sparamp->addr_sem = server_sem;
  sparamp->exit_mon = mon2;
  sparamp->exit_counter = &thread_count;
  sparamp->datalen = datalen;
  t = PR_CreateThread(PR_USER_THREAD, TransmitFile_Server, (void*)sparamp,
                      PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_UNJOINABLE_THREAD,
                      0);
  if (t == NULL) {
    fprintf(stderr, "prsocket_test: PR_CreateThread failed\n");
    failed_already = 1;
    rv = -1;
    goto done;
  }
  DPRINTF(("Created TCP server = 0x%x\n", t));
  thread_count++;

  /*
   * wait till the server address is setup
   */
  PR_WaitSem(server_sem);

  /*
   * Now start a bunch of client threads
   */

  cparamp = PR_NEW(Client_Param);
  if (cparamp == NULL) {
    fprintf(stderr, "prsocket_test: PR_NEW failed\n");
    failed_already = 1;
    rv = -1;
    goto done;
  }
  cparamp->server_addr = tcp_server_addr;
  cparamp->server_addr.inet.ip = PR_htonl(PR_INADDR_LOOPBACK);
  cparamp->exit_mon = mon2;
  cparamp->exit_counter = &thread_count;
  cparamp->datalen = datalen;
  for (i = 0; i < num_transmitfile_clients; i++) {
    t = create_new_thread(PR_USER_THREAD, TransmitFile_Client, (void*)cparamp,
                          PR_PRIORITY_NORMAL, PR_LOCAL_THREAD,
                          PR_UNJOINABLE_THREAD, 0, i);
    if (t == NULL) {
      fprintf(stderr, "prsocket_test: PR_CreateThread failed\n");
      rv = -1;
      failed_already = 1;
      goto done;
    }
    DPRINTF(("Created TransmitFile client = 0x%lx\n", t));
    thread_count++;
  }
  /* Wait for server and client threads to exit */
  while (thread_count) {
    PR_Wait(mon2, PR_INTERVAL_NO_TIMEOUT);
    DPRINTF(("Socket_Misc_Test - thread_count  = %d\n", thread_count));
  }
  PR_ExitMonitor(mon2);
done:
  if (buf) {
    PR_DELETE(buf);
  }
#if defined(XP_UNIX)
  munmap((char*)small_file_addr, SMALL_FILE_SIZE);
  munmap((char*)large_file_addr, LARGE_FILE_SIZE);
#endif
  PR_Close(small_file_fd);
  PR_Close(large_file_fd);
  if ((PR_Delete(SMALL_FILE_NAME)) == PR_FAILURE) {
    fprintf(stderr, "prsocket_test: failed to unlink file %s\n",
            SMALL_FILE_NAME);
    failed_already = 1;
  }
  if ((PR_Delete(LARGE_FILE_NAME)) == PR_FAILURE) {
    fprintf(stderr, "prsocket_test: failed to unlink file %s\n",
            LARGE_FILE_NAME);
    failed_already = 1;
  }
  if ((PR_RmDir(TEST_DIR)) == PR_FAILURE) {
    fprintf(stderr, "prsocket_test failed to rmdir %s: (%ld, %ld)\n", TEST_DIR,
            PR_GetError(), PR_GetOSError());
    failed_already = 1;
  }

  printf("%-29s%s", "Socket_Misc_Test", ":");
  printf("%2d Server %2d Clients\n", 1, num_transmitfile_clients);
  printf("%30s Sizes of Transmitted Files  - %4d KB, %2d MB \n", ":",
         SMALL_FILE_SIZE / 1024, LARGE_FILE_SIZE / (1024 * 1024));

  return rv;
}
/************************************************************************/

/*
 * Test Socket NSPR APIs
 */

int main(int argc, char** argv) {
  /*
   * -d           debug mode
   */

  PLOptStatus os;
  PLOptState* opt = PL_CreateOptState(argc, argv, "d");
  while (PL_OPT_EOL != (os = PL_GetNextOpt(opt))) {
    if (PL_OPT_BAD == os) {
      continue;
    }
    switch (opt->option) {
      case 'd': /* debug mode */
        _debug_on = 1;
        break;
      default:
        break;
    }
  }
  PL_DestroyOptState(opt);

  PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);

  PR_SetConcurrency(4);

  emuSendFileIdentity = PR_GetUniqueIdentity("Emulated SendFile");
  emuSendFileMethods = *PR_GetDefaultIOMethods();
  emuSendFileMethods.transmitfile = emu_TransmitFile;
  emuSendFileMethods.sendfile = emu_SendFile;

  /*
   * run client-server test with TCP, Ipv4-Ipv4
   */
  printf("TCP Client/Server Test - IPv4/Ipv4\n");
  if (TCP_Socket_Client_Server_Test() < 0) {
    printf("TCP_Socket_Client_Server_Test failed\n");
    goto done;
  } else {
    printf("TCP_Socket_Client_Server_Test Passed\n");
  }
  /*
   * client-server test, Ipv6-Ipv4
   */
  client_domain = PR_AF_INET6;
  printf("TCP Client/Server Test - IPv6/Ipv4\n");
  if (TCP_Socket_Client_Server_Test() < 0) {
    printf("TCP_Socket_Client_Server_Test failed\n");
    goto done;
  } else {
    printf("TCP_Socket_Client_Server_Test Passed\n");
  }
  /*
   * client-server test, Ipv4-Ipv6
   */
  client_domain = PR_AF_INET;
  server_domain = PR_AF_INET6;
  printf("TCP Client/Server Test - IPv4/Ipv6\n");
  if (TCP_Socket_Client_Server_Test() < 0) {
    printf("TCP_Socket_Client_Server_Test failed\n");
    goto done;
  } else {
    printf("TCP_Socket_Client_Server_Test Passed\n");
  }
  /*
   * client-server test, Ipv6-Ipv6
   */
  client_domain = PR_AF_INET6;
  server_domain = PR_AF_INET6;
  printf("TCP Client/Server Test - IPv6/Ipv6\n");
  if (TCP_Socket_Client_Server_Test() < 0) {
    printf("TCP_Socket_Client_Server_Test failed\n");
    goto done;
  } else {
    printf("TCP_Socket_Client_Server_Test Passed\n");
  }
  test_cancelio = 0;

  /*
   * Misc socket tests - including transmitfile, etc.
   */

  /* File transmission test can not be done in Symbian OS because of
   * large file's size and the incomplete mmap() implementation. */
  /*
  ** The 'transmit file' test does not run because
  ** transmit file is not implemented in NSPR yet.
  **
  */
  if (Socket_Misc_Test() < 0) {
    printf("Socket_Misc_Test failed\n");
    failed_already = 1;
    goto done;
  } else {
    printf("Socket_Misc_Test passed\n");
  }

  /*
   * run client-server test with TCP again to test
   * recycling used sockets from PR_TransmitFile().
   */
  if (TCP_Socket_Client_Server_Test() < 0) {
    printf("TCP_Socket_Client_Server_Test failed\n");
    goto done;
  } else {
    printf("TCP_Socket_Client_Server_Test Passed\n");
  }

done:
  PR_Cleanup();
  if (failed_already) {
    return 1;
  } else {
    return 0;
  }
}
