/* -*- 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/. */

/*******************************************************************
** udpsrc.c -- Test basic function of UDP server
**
** udpsrv operates on the same machine with program udpclt.
** udpsrv is the server side of a udp sockets application.
** udpclt is the client side of a udp sockets application.
**
** The test is designed to assist developers in porting/debugging
** the UDP socket functions of NSPR.
**
** This test is not a stress test.
**
** main() starts two threads: UDP_Server() and UDP_Client();
** main() uses PR_JoinThread() to wait for the threads to complete.
**
** UDP_Server() does repeated recvfrom()s from a socket.
** He detects an EOF condition set by UDP_Client(). For each
** packet received by UDP_Server(), he checks its content for
** expected content, then sends the packet back to UDP_Client().
**
** UDP_Client() sends packets to UDP_Server() using sendto()
** he recieves packets back from the server via recvfrom().
** After he sends enough packets containing UDP_AMOUNT_TO_WRITE
** bytes of data, he sends an EOF message.
**
** The test issues a pass/fail message at end.
**
** Notes:
** The variable "_debug_on" can be set to 1 to cause diagnostic
** messages related to client/server synchronization. Useful when
** the test hangs.
**
** Error messages are written to stdout.
**
********************************************************************
*/
/* --- include files --- */
#include "nspr.h"
#include "prpriv.h"

#include "plgetopt.h"
#include "prttools.h"

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

#ifdef XP_PC
#  define mode_t int
#endif

#define UDP_BUF_SIZE 4096
#define UDP_DGRAM_SIZE 128
#define UDP_AMOUNT_TO_WRITE (PRInt32)((UDP_DGRAM_SIZE * 1000l) + 1)
#define NUM_UDP_CLIENTS 1
#define NUM_UDP_DATAGRAMS_PER_CLIENT 5
#define UDP_SERVER_PORT 9050
#define UDP_CLIENT_PORT 9053
#define MY_INADDR PR_INADDR_ANY
#define PEER_INADDR PR_INADDR_LOOPBACK

#define UDP_TIMEOUT 400000
/* #define UDP_TIMEOUT             PR_INTERVAL_NO_TIMEOUT */

/* --- static data --- */
static PRIntn _debug_on = 0;
static PRBool passed = PR_TRUE;
static PRUint32 cltBytesRead = 0;
static PRUint32 srvBytesRead = 0;
static PRFileDesc* output = NULL;

/* --- static function declarations --- */
#define DPRINTF(arg) \
  if (_debug_on) PR_fprintf(output, arg)

/*******************************************************************
** ListNetAddr() -- Display the Net Address on stdout
**
** Description: displays the component parts of a PRNetAddr struct
**
** Arguments:   address of PRNetAddr structure to display
**
** Returns: void
**
** Notes:
**
********************************************************************
*/
void ListNetAddr(char* msg, PRNetAddr* na) {
  char mbuf[256];

  sprintf(mbuf, "ListNetAddr: %s family: %d, port: %d, ip: %8.8X\n", msg,
          na->inet.family, PR_ntohs(na->inet.port), PR_ntohl(na->inet.ip));
#if 0
    DPRINTF( mbuf );
#endif
} /* --- end ListNetAddr() --- */

/********************************************************************
** UDP_Server() -- Test a UDP server application
**
** Description: The Server side of a UDP Client/Server application.
**
** Arguments: none
**
** Returns: void
**
** Notes:
**
**
********************************************************************
*/
static void PR_CALLBACK UDP_Server(void* arg) {
  static char svrBuf[UDP_BUF_SIZE];
  PRFileDesc* svrSock;
  PRInt32 rv;
  PRNetAddr netaddr;
  PRBool bound = PR_FALSE;
  PRBool endOfInput = PR_FALSE;
  PRInt32 numBytes = UDP_DGRAM_SIZE;

  DPRINTF("udpsrv: UDP_Server(): starting\n");

  /* --- Create the socket --- */
  DPRINTF("udpsrv: UDP_Server(): Creating UDP Socket\n");
  svrSock = PR_NewUDPSocket();
  if (svrSock == NULL) {
    passed = PR_FALSE;
    if (debug_mode)
      PR_fprintf(output,
                 "udpsrv: UDP_Server(): PR_NewUDPSocket() returned NULL\n");
    return;
  }

  /* --- Initialize the sockaddr_in structure --- */
  memset(&netaddr, 0, sizeof(netaddr));
  netaddr.inet.family = PR_AF_INET;
  netaddr.inet.port = PR_htons(UDP_SERVER_PORT);
  netaddr.inet.ip = PR_htonl(MY_INADDR);

  /* --- Bind the socket --- */
  while (!bound) {
    DPRINTF("udpsrv: UDP_Server(): Binding socket\n");
    rv = PR_Bind(svrSock, &netaddr);
    if (rv < 0) {
      if (PR_GetError() == PR_ADDRESS_IN_USE_ERROR) {
        if (debug_mode)
          PR_fprintf(output,
                     "udpsrv: UDP_Server(): \
						PR_Bind(): reports: PR_ADDRESS_IN_USE_ERROR\n");
        PR_Sleep(PR_MillisecondsToInterval(2000));
        continue;
      } else {
        passed = PR_FALSE;
        if (debug_mode)
          PR_fprintf(output,
                     "udpsrv: UDP_Server(): \
						PR_Bind(): failed: %ld with error: %ld\n",
                     rv, PR_GetError());
        PR_Close(svrSock);
        return;
      }
    } else {
      bound = PR_TRUE;
    }
  }
  ListNetAddr("UDP_Server: after bind", &netaddr);

  /* --- Recv the socket --- */
  while (!endOfInput) {
    DPRINTF("udpsrv: UDP_Server(): RecvFrom() socket\n");
    rv = PR_RecvFrom(svrSock, svrBuf, numBytes, 0, &netaddr, UDP_TIMEOUT);
    if (rv == -1) {
      passed = PR_FALSE;
      if (debug_mode)
        PR_fprintf(
            output,
            "udpsrv: UDP_Server(): PR_RecvFrom(): failed with error: %ld\n",
            PR_GetError());
      PR_Close(svrSock);
      return;
    }
    ListNetAddr("UDP_Server after RecvFrom", &netaddr);

    srvBytesRead += rv;

    if (svrBuf[0] == 'E') {
      DPRINTF("udpsrv: UDP_Server(): EOF on input detected\n");
      endOfInput = PR_TRUE;
    }

    /* --- Send the socket --- */
    DPRINTF("udpsrv: UDP_Server(): SendTo(): socket\n");
    rv = PR_SendTo(svrSock, svrBuf, rv, 0, &netaddr, PR_INTERVAL_NO_TIMEOUT);
    if (rv == -1) {
      passed = PR_FALSE;
      if (debug_mode)
        PR_fprintf(
            output,
            "udpsrv: UDP_Server(): PR_SendTo(): failed with error: %ld\n",
            PR_GetError());
      PR_Close(svrSock);
      return;
    }
    ListNetAddr("UDP_Server after SendTo", &netaddr);
  }

  /* --- Close the socket --- */
  DPRINTF("udpsrv: UDP_Server(): Closing socket\n");
  rv = PR_Close(svrSock);
  if (rv != PR_SUCCESS) {
    passed = PR_FALSE;
    if (debug_mode)
      PR_fprintf(output,
                 "udpsrv: UDP_Server(): PR_Close(): failed to close socket\n");
    return;
  }

  DPRINTF("udpsrv: UDP_Server(): Normal end\n");
} /* --- end UDP_Server() --- */

static char cltBuf[UDP_BUF_SIZE];
static char cltBufin[UDP_BUF_SIZE];
/********************************************************************
** UDP_Client() -- Test a UDP client application
**
** Description:
**
** Arguments:
**
**
** Returns:
** 0 -- Successful execution
** 1 -- Test failed.
**
** Notes:
**
**
********************************************************************
*/
static void PR_CALLBACK UDP_Client(void* arg) {
  PRFileDesc* cltSock;
  PRInt32 rv;
  PRBool bound = PR_FALSE;
  PRNetAddr netaddr;
  PRNetAddr netaddrx;
  PRBool endOfInput = PR_FALSE;
  PRInt32 numBytes = UDP_DGRAM_SIZE;
  PRInt32 writeThisMany = UDP_AMOUNT_TO_WRITE;
  int i;

  DPRINTF("udpsrv: UDP_Client(): starting\n");

  /* --- Create the socket --- */
  cltSock = PR_NewUDPSocket();
  if (cltSock == NULL) {
    passed = PR_FALSE;
    if (debug_mode)
      PR_fprintf(output,
                 "udpsrv: UDP_Client(): PR_NewUDPSocket() returned NULL\n");
    return;
  }

  /* --- Initialize the sockaddr_in structure --- */
  memset(&netaddr, 0, sizeof(netaddr));
  netaddr.inet.family = PR_AF_INET;
  netaddr.inet.ip = PR_htonl(MY_INADDR);
  netaddr.inet.port = PR_htons(UDP_CLIENT_PORT);

  /* --- Initialize the write buffer --- */
  for (i = 0; i < UDP_BUF_SIZE; i++) {
    cltBuf[i] = i;
  }

  /* --- Bind the socket --- */
  while (!bound) {
    DPRINTF("udpsrv: UDP_Client(): Binding socket\n");
    rv = PR_Bind(cltSock, &netaddr);
    if (rv < 0) {
      if (PR_GetError() == PR_ADDRESS_IN_USE_ERROR) {
        if (debug_mode)
          PR_fprintf(output,
                     "udpsrv: UDP_Client(): PR_Bind(): reports: "
                     "PR_ADDRESS_IN_USE_ERROR\n");
        PR_Sleep(PR_MillisecondsToInterval(2000));
        continue;
      } else {
        passed = PR_FALSE;
        if (debug_mode)
          PR_fprintf(
              output,
              "udpsrv: UDP_Client(): PR_Bind(): failed: %ld with error: %ld\n",
              rv, PR_GetError());
        PR_Close(cltSock);
        return;
      }
    } else {
      bound = PR_TRUE;
    }
  }
  ListNetAddr("UDP_Client after Bind", &netaddr);

  /* --- Initialize the sockaddr_in structure --- */
  memset(&netaddr, 0, sizeof(netaddr));
  netaddr.inet.family = PR_AF_INET;
  netaddr.inet.ip = PR_htonl(PEER_INADDR);
  netaddr.inet.port = PR_htons(UDP_SERVER_PORT);

  /* --- send and receive packets until no more data left */
  while (!endOfInput) {
    /*
    ** Signal EOF in the data stream on the last packet
    */
    if (writeThisMany <= UDP_DGRAM_SIZE) {
      DPRINTF("udpsrv: UDP_Client(): Send EOF packet\n");
      cltBuf[0] = 'E';
      endOfInput = PR_TRUE;
    }

    /* --- SendTo the socket --- */
    if (writeThisMany > UDP_DGRAM_SIZE) {
      numBytes = UDP_DGRAM_SIZE;
    } else {
      numBytes = writeThisMany;
    }
    writeThisMany -= numBytes;
    {
      char mbuf[256];
      sprintf(mbuf, "udpsrv: UDP_Client(): write_this_many: %d, numbytes: %d\n",
              writeThisMany, numBytes);
      DPRINTF(mbuf);
    }

    DPRINTF("udpsrv: UDP_Client(): SendTo(): socket\n");
    rv = PR_SendTo(cltSock, cltBuf, numBytes, 0, &netaddr, UDP_TIMEOUT);
    if (rv == -1) {
      passed = PR_FALSE;
      if (debug_mode)
        PR_fprintf(
            output,
            "udpsrv: UDP_Client(): PR_SendTo(): failed with error: %ld\n",
            PR_GetError());
      PR_Close(cltSock);
      return;
    }
    ListNetAddr("UDP_Client after SendTo", &netaddr);

    /* --- RecvFrom the socket --- */
    memset(cltBufin, 0, UDP_BUF_SIZE);
    DPRINTF("udpsrv: UDP_Client(): RecvFrom(): socket\n");
    rv = PR_RecvFrom(cltSock, cltBufin, numBytes, 0, &netaddrx, UDP_TIMEOUT);
    if (rv == -1) {
      passed = PR_FALSE;
      if (debug_mode)
        PR_fprintf(
            output,
            "udpsrv: UDP_Client(): PR_RecvFrom(): failed with error: %ld\n",
            PR_GetError());
      PR_Close(cltSock);
      return;
    }
    ListNetAddr("UDP_Client after RecvFrom()", &netaddr);
    cltBytesRead += rv;

    /* --- verify buffer --- */
    for (i = 0; i < rv; i++) {
      if (cltBufin[i] != i) {
        /* --- special case, end of input --- */
        if (endOfInput && i == 0 && cltBufin[0] == 'E') {
          continue;
        }
        passed = PR_FALSE;
        if (debug_mode)
          PR_fprintf(output, "udpsrv: UDP_Client(): return data mismatch\n");
        PR_Close(cltSock);
        return;
      }
    }
    if (debug_mode) {
      PR_fprintf(output, ".");
    }
  }

  /* --- Close the socket --- */
  DPRINTF("udpsrv: UDP_Server(): Closing socket\n");
  rv = PR_Close(cltSock);
  if (rv != PR_SUCCESS) {
    passed = PR_FALSE;
    if (debug_mode)
      PR_fprintf(output,
                 "udpsrv: UDP_Client(): PR_Close(): failed to close socket\n");
    return;
  }
  DPRINTF("udpsrv: UDP_Client(): ending\n");
} /* --- end UDP_Client() --- */

/********************************************************************
** main() -- udpsrv
**
** arguments:
**
** Returns:
** 0 -- Successful execution
** 1 -- Test failed.
**
** Description:
**
** Standard test case setup.
**
** Calls the function UDP_Server()
**
********************************************************************
*/

int main(int argc, char** argv) {
  PRThread *srv, *clt;
  /* The command line argument: -d is used to determine if the test is being run
      in debug mode. The regress tool requires only one line output:PASS or
     FAIL. All of the printfs associated with this test has been handled with a
     if (debug_mode) test. Usage: test_name -d -v
      */
  PLOptStatus os;
  PLOptState* opt = PL_CreateOptState(argc, argv, "dv");
  while (PL_OPT_EOL != (os = PL_GetNextOpt(opt))) {
    if (PL_OPT_BAD == os) {
      continue;
    }
    switch (opt->option) {
      case 'd': /* debug mode */
        debug_mode = 1;
        break;
      case 'v': /* verbose mode */
        _debug_on = 1;
        break;
      default:
        break;
    }
  }
  PL_DestroyOptState(opt);

  PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
  output = PR_STDERR;

  PR_SetConcurrency(4);

  /*
  ** Create the Server thread
  */
  DPRINTF("udpsrv: Creating Server Thread\n");
  srv = PR_CreateThread(PR_USER_THREAD, UDP_Server, (void*)0, PR_PRIORITY_LOW,
                        PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);
  if (srv == NULL) {
    if (debug_mode) {
      PR_fprintf(output, "udpsrv: Cannot create server thread\n");
    }
    passed = PR_FALSE;
  }

  /*
  ** Give the Server time to Start
  */
  DPRINTF("udpsrv: Pausing to allow Server to start\n");
  PR_Sleep(PR_MillisecondsToInterval(200));

  /*
  ** Create the Client thread
  */
  DPRINTF("udpsrv: Creating Client Thread\n");
  clt = PR_CreateThread(PR_USER_THREAD, UDP_Client, (void*)0, PR_PRIORITY_LOW,
                        PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);
  if (clt == NULL) {
    if (debug_mode) {
      PR_fprintf(output, "udpsrv: Cannot create server thread\n");
    }
    passed = PR_FALSE;
  }

  /*
  **
  */
  DPRINTF("udpsrv: Waiting to join Server & Client Threads\n");
  PR_JoinThread(srv);
  PR_JoinThread(clt);

  /*
  ** Evaluate test results
  */
  if (debug_mode)
    PR_fprintf(output,
               "\n\nudpsrv: main(): cltBytesRead(%ld), \
		srvBytesRead(%ld), expected(%ld)\n",
               cltBytesRead, srvBytesRead, UDP_AMOUNT_TO_WRITE);
  if (cltBytesRead != srvBytesRead || cltBytesRead != UDP_AMOUNT_TO_WRITE) {
    passed = PR_FALSE;
  }
  PR_Cleanup();
  if (passed) {
    return 0;
  } else {
    return 1;
  }
} /* --- end main() --- */
