/******************************************************************************
FILE:  NetDiagnostics.c

SERVICES:  Network Diagnostics example application

GENERAL DESCRIPTION:

PUBLIC CLASSES AND STATIC FUNCTIONS:
	Uses of INetMgr, ISocket, IHtmlViewer, IWeb, IWebOpts, IWebResp, ISource, 
   ISourceUtil, IPeek, IGetLine interfaces. 

INITIALIZATION & SEQUENCING REQUIREMENTS:

	See Exported Routines

     Copyright ?1999-2002 QUALCOMM Incorporated.
               All Rights Reserved.
            QUALCOMM Proprietary/GTDR
******************************************************************************/

//*****************************************************************************
// INCLUDE FILES
//*****************************************************************************
#include "AEEModGen.h"
#include "AEEAppGen.h"
#include "AEEFile.h"
#include "AEEHtmlViewer.h"
#include "AEEMenu.h"
#include "AEEShell.h"
#include "AEEStdLib.h"
#include "AEEText.h"
#include "AEEWeb.h"

#include "NetDiagnostics.bid"
#include "NetDiagnostics.brh"
#include "nmdef.h"

//*****************************************************************************
// DEFINITIONS
//*****************************************************************************

#define MAX_RES_STRING_BUF_SIZE               200  // Used for allocating resource string buffers
#define SPLASH_TIMER_DURATION                 750  // Number of milliseconds the splash screen is displayed
#define MAX_HIST                                9

#define IPPORT_ECHO                           7

// Special URL bases
#define JUMP_ECHOTEST      "test:echo"                // starts echo test (expects form data)
#define JUMP_HTTPTEST      "test:http"                // starts HTTP test (expects form data)
#define JUMP_MAIN          "file:///ND_MainMenu.htm"

#define WEBBER_USERAGENT   "NetDiagnostics/1.0 (built on "__DATE__")\r\n"

#define STREQ(a,b)         (!STRCMP(a,b))

#define FORALL(p,a)        for (p = (a) + ARRAY_SIZE(a) - 1; p >= (a); --p)

#define FOR_ALL_WEBACTIONS(pApp, var, exp)     {WebAction *var; FORALL(var, (pApp)->m_awa) { exp; } }


//*****************************************************************************
// ENUMERATED TYPE DEFINITIONS
//*****************************************************************************


//*****************************************************************************
// GLOBAL APPLICATION DATA STRUCTURES
//*****************************************************************************
typedef struct _CNetDiagnosticsApp CNetDiagnosticsApp;

typedef void (*PFNCLEANUP)(CNetDiagnosticsApp *);

// A WebAction holds state necessary to perform 1 IWeb transaction

typedef struct WebAction
{
   CNetDiagnosticsApp * pParent;          // my parent
   AEECallback          cb;            // how to call me back
              
   IWebResp *           piWResp;       // the answer I get from IWeb
   IGetLine *           piGetLine;     // the body of the response, if any
   int                  nLines;        // count of lines in response
   int                  nBytes;        // count of bytes in response

   uint32               uStart;
   uint32               uRecvStart;

   IPeek                *pipPostData;  // POST stream, if any
   char                 *pszPostData;  // POST string

} WebAction;

// Applet Struct

struct _CNetDiagnosticsApp
{
   AEEApplet            a;                      // Mandatory AEEApplet data member appears first
   IFileMgr *           m_pFileMgr;             // Pointer to the apps IFileMgr object
   AEERect              m_rc;                   // Device Screen Rect     
   IHtmlViewer *        m_pHTMLViewer;          // The HTMLViewer control used to display HTML text
   INetMgr *            m_pINetMgr;             // Pointer to INetMgr
   IStatic *            m_pIStatic;             // Used for displaying messages
   char *               m_ppszHistory[MAX_HIST]; // history list
   int                  m_cntHistory;           // number of entries currently in the history list
   PFNCLEANUP           m_pfnViewCleanup;       // function to clean up after test (NULL => no test)

   // Form data

   char *               m_pszFormData;          // this points to the memory block holding all values
   char *               m_pszHost;              // "HOST" field
   char *               m_pszURL;               // "URL" field
   char *               m_pszMsg;               // "MSG" field
   flg                  m_bRS:1;                // "RS" field: Display response if TRUE
   flg                  m_bRT:1;                // "RT" field: Display response time if TRUE
   flg                  m_bTCP:1;               // "PROTOCOL" field == "1":  Use TCP as protocol if TRUE
   uint16               m_nDataLength;          // length of pszMsg (calculated when set)

   // Echoer state
   
   uint16               m_nTCPIdx;              // Index used for sending TCP data
   uint32               m_nTotalTime;           // Total Time
   uint32               m_nSendTime;            // Time to Send Data
   uint32               m_nReceiveTime;         // Time to Receive Data
   AEEDNSResult         m_dnsr;                 // DNS Result
   AEECallback          m_cb;                   // Callback for DNS
   ISocket *            m_pISocket;             // Pointer to socket

   // HTTP test state

   IWeb *               m_pIWeb;                // Pointer to IWeb for HTTP Tests
   WebAction            m_awa[1];               // array of WebActions.  By changing the '1' to any 
                                                // number, you can attempt to kick off multiple Web 
                                                // transactions simultaneously, testing Keep-Alive,
                                                // multiple-POST Keep-Alives, NOWAITCONN, FORCENEW, 
                                                // and request queueing.
};

//*****************************************************************************
// FUNCTION DECLARATIONS
//*****************************************************************************

static boolean ND_InitAppData(CNetDiagnosticsApp * pApp);
static void    ND_FreeAppData(CNetDiagnosticsApp * pApp);

static boolean ND_HandleEvent(CNetDiagnosticsApp * pApp, AEEEvent eCode, uint16 wParam, uint32 dwParam);

static void    ND_NotifyCB( void* pvUser, HViewNotify* pHViewNotify );
static void    ND_GoTo(CNetDiagnosticsApp *pApp, const char *pszURL);

static void    ND_DisplaySplashScreen(CNetDiagnosticsApp * pApp);
static void    ND_DisplaySplashScreenCB(CNetDiagnosticsApp* pApp);
static void    ND_DisplayMenu(CNetDiagnosticsApp* pApp, const char *pszURL);
static void    ND_EchoCleanup(CNetDiagnosticsApp* pApp);
static void    ND_HTTPCleanup(CNetDiagnosticsApp* pApp);
static void    ND_StartTest(CNetDiagnosticsApp* pApp, const char* pszURL);

static void    Echoer_Start(CNetDiagnosticsApp * pApp);
static void    Echoer_TCPConnected(void *p, int nErr);
static void    Echoer_TCPConnect(CNetDiagnosticsApp * pApp);
static void    Echoer_TCPDNSConnect(void *p);
static void    Echoer_TCPWrite(CNetDiagnosticsApp * pApp);
static void    Echoer_TCPRead(CNetDiagnosticsApp * pApp);
static void    Echoer_UDPWrite(CNetDiagnosticsApp * pApp);
static void    Echoer_UDPDNSWrite(void *p);
static void    Echoer_UDPRead(CNetDiagnosticsApp * pApp);

static void    WebAction_Start(WebAction *pwa, char *pszUrl);
static void    WebAction_GotResp(void *p);
static void    WebAction_Header(void *p, const char *cpszName, GetLine *pglVal);
static void    WebAction_Status(void *p, WebStatus ws, void *pVal);
static void    WebAction_Stop(WebAction *pwa);
static void    WebAction_ReadLines(void *p);

//*****************************************************************************
// UTILITY FUNCTIONS
//****************************************************************************


// Free the old pointer and fill with STRDUP(pszNew)
//
static void StrReplace(char **ppsz, const char *pszNew)
{
   FREE(*ppsz);   // FREE(NULL) is okay in BREW
   *ppsz = (pszNew ? STRDUP(pszNew) : (char*)pszNew);
}


static void ND_Print(CNetDiagnosticsApp *pApp, char *pszFmt, ...)
{
   char buf[100];
   va_list argptr;
   va_start(argptr, pszFmt);

   if( pApp->m_pIStatic )
   {
      if (VSNPRINTF(buf, sizeof(buf), pszFmt, argptr) >= 0) {
         ISTATIC_SetTextEx(pApp->m_pIStatic, (byte*)buf, NULL, TRUE);
         ISTATIC_Redraw(pApp->m_pIStatic);
      }
   }
   va_end(argptr);
}


//*****************************************************************************
// APPLICATION-WIDE INITIALIZATION/CLEANUP FUNCTIONS
//****************************************************************************

/*===========================================================================

FUNCTION: AEEClsCreateInstance

DESCRIPTION:
	This function is invoked while the app is being loaded. All Modules must provide this 
	function. Ensure to retain the same name and parameters for this function.
	In here, the module must verify the ClassID and then invoke the AEEApplet_New() function
	that has been provided in AEEAppGen.c. 

  After invoking AEEApplet_New(), this function can do app specific initialization. In this
  example, a generic structure is provided so that app developers need not change app specific
  initialization section every time except for a call to ND_InitAppData(). 
  This is done as follows: ND_InitAppData() is called to initialize AppletData 
  instance. It is app developers responsibility to fill-in app data initialization 
  code of ND_InitAppData(). App developer is also responsible to release memory 
  allocated for data contained in AppletData -- this can be done in 
  ND_FreeAppData().

PARAMETERS:
	clsID [in] - Specifies the ClassID of the applet which is being loaded

	pIShell [in] - Contains pointer to the IShell interface. 

	pIModule [pin] - Contains pointer to the IModule interface to the current module to which
    this app belongs

	ppObj [out] - On return, *ppObj must point to a valid IApplet structure. Allocation
	  of memory for this structure and initializing the base data members is done by AEEApplet_New().

DEPENDENCIES:
  None

RETURN VALUE:
  AEE_SUCCESS - If the app needs to be loaded and if AEEApplet_New() invocation was
     successful
  EFAILED - If the app does not need to be loaded or if errors occurred in 
     AEEApplet_New(). If this function returns FALSE, the app will not be loaded.

SIDE EFFECTS:
  None
===========================================================================*/
int AEEClsCreateInstance(AEECLSID ClsId,IShell * pIShell,IModule * po,void ** ppObj)
{
   CNetDiagnosticsApp *   pApp = NULL; 

   if(!AEEApplet_New(sizeof(CNetDiagnosticsApp), ClsId, pIShell,po,(IApplet**)ppObj,(AEEHANDLER)ND_HandleEvent,(PFNFREEAPPDATA)ND_FreeAppData))
      return ENOMEMORY;
	
   pApp = (CNetDiagnosticsApp *)*ppObj;

   if (!ND_InitAppData(pApp)) {
      *ppObj = NULL;
		return EFAILED;
   }

   return(AEE_SUCCESS);
}

/*===========================================================================

FUNCTION: ND_InitAppData

DESCRIPTION:
  This function initializes app specific data allocates memory for app data 
  (AppletData) and sets it to pAppData of AEEApplet. It also functions to 
  create all necessary GUI objects.

PARAMETERS:
	pi [in] - Pointer to the IApplet structure. This structure contains 
    information specific to this applet. It was initialized during the 
    AEEClsCreateInstance().

DEPENDENCIES:
  Assumes pi is not NULL

RETURN VALUE:
  TRUE: If the app has app data is allocated and initialized successfully
  FALSE: Either app data could not be allocated or initialzied

SIDE EFFECTS:
  None
===========================================================================*/
static boolean ND_InitAppData(CNetDiagnosticsApp * pApp)
{
   AEEDeviceInfo di;
   IShell * pIShell = pApp->a.m_pIShell;

   // Create each of the controls used by the application.
   if((ISHELL_CreateInstance(pIShell, AEECLSID_FILEMGR, (void**)(&pApp->m_pFileMgr)) != SUCCESS) ||
      (ISHELL_CreateInstance(pIShell, AEECLSID_HTML, (void**)(&pApp->m_pHTMLViewer)) != SUCCESS) ||
      (ISHELL_CreateInstance(pIShell, AEECLSID_NET, (void**)(&pApp->m_pINetMgr)) != SUCCESS) ||
      (ISHELL_CreateInstance(pIShell, AEECLSID_WEB, (void **)(&pApp->m_pIWeb)) != SUCCESS))
   {
      IAPPLET_Release((IApplet*)pApp);
      return FALSE;
   }

   // Set callback for HTML viewer
   IHTMLVIEWER_SetNotifyFn(pApp->m_pHTMLViewer, (PFNHVIEWNOTIFY)ND_NotifyCB, pApp);
   IHTMLVIEWER_SetProperties(pApp->m_pHTMLViewer, HVP_SCROLLBAR);

   // Get device screen rect
   ISHELL_GetDeviceInfo(pApp->a.m_pIShell, &di);
   SETAEERECT(&pApp->m_rc, 0, 0, di.cxScreen, di.cyScreen);

   pApp->m_nTotalTime = pApp->m_nSendTime = pApp->m_nReceiveTime = 0;

   // Set Default
   pApp->m_bRS = pApp->m_bRT = pApp->m_bTCP = FALSE;

   // Initialize the IWeb with a few options
   {
      int    i = 0;
      WebOpt awo[10]; 

      // set the IWeb connect timeout to 10 seconds.  this also sets the 
      // failover timeout, if unset, or set to 0, IWeb uses the system 
      // default (30 seconds unless an OEM changes it)
      awo[i].nId  = WEBOPT_CONNECTTIMEOUT;
      awo[i].pVal = (void *)10000;
      i++;

      // test user-agent, uncomment this section to ship your own user-agent 
      // string. if unset, IWeb will send a default.  If set to NULL, no 
      // user agent header will be sent */

      // Set TEST_USER_AGENT in the NetDiagnostics project settings to all
      // shipping of your own user agent.

#ifdef TEST_USER_AGENT
      awo[i].nId  = WEBOPT_USERAGENT;
      awo[i].pVal = (void *)WEBBER_USERAGENT;
      i++;
#endif 

      // test nowaitconn, this only comes into effect if you build webber 
      // with multiple WebActions (see the definition of struct Webber)
      awo[i].nId  = WEBOPT_FLAGS;
      awo[i].pVal = (void *)WEBREQUEST_NOWAITCONN;
      i++;
      

      // test forcenew, uncomment this section to try multiple simultaneous
      // "forced" new connections. Forced new connections are not kept alive
      // unless they are the first forced new connection to a host
#ifdef TEST_FORCENEWCONN
      awo[i].nId  = WEBOPT_FLAGS;
      awo[i].pVal = (void *)WEBREQUEST_FORCENEWCONN;
      i++;
#endif 


      // turn off HTTP over HTTP proxying
      awo[i].nId  = WEBOPT_PROXYSPEC;
      awo[i].pVal = (void *)"http:///";
      i++;

      // turn on ALL proxying.  Proxyspecs are examined in WebOpt
      // order, so in this list, with the above and below PROXYSPECs,
      // everything except HTTP will be proxied through
      // http://webproxy.yourdomain.com:8080, (which you'll have to
      // set up to test, sorry
      awo[i].nId  = WEBOPT_PROXYSPEC;
      awo[i].pVal = (void *)"*:///http://webproxy.yourdomain.com:8080";
      i++;

      // Marks the end of the array of WebOpts
      awo[i].nId  = WEBOPT_END;
      
      // Add Options
      IWEB_AddOpt(pApp->m_pIWeb,awo);
   }

   // Initialize all my WebActions to point to NetDiagnostics applet
   FOR_ALL_WEBACTIONS(pApp, p, p->pParent = pApp);

   return TRUE;
}

/*===========================================================================

FUNCTION: ND_FreeAppData

DESCRIPTION:
  Frees data contained in app data and memory for app data itself.
  App developer needs to free data contained in AppletData.

PARAMETERS:
	pi [in] - Pointer to the IApplet structure. This structure contains 
    information specific to this applet. It was initialized during the 
    AEEClsCreateInstance().

DEPENDENCIES:
   Assumes pi is not NULL

RETURN VALUE:
   None

SIDE EFFECTS:
   None
===========================================================================*/
static void ReleaseObj(void ** ppObj)
{
   if (*ppObj) {
      (void)IBASE_Release(((IBase *)*ppObj));
      *ppObj = NULL;
   }
}

static void ND_FreeAppData( CNetDiagnosticsApp* pApp )
{
   int i;

   // Socket callbacks: Deleting the socket cancels any callbacks.  During app
   // cleanup, *all* references to the socket must be released, so the socket
   // will be deleted and canceling callbacks is not necessary.

   if (pApp->m_pfnViewCleanup) {
      pApp->m_pfnViewCleanup(pApp);
   }

   for (i = 0; i < ARRAY_SIZE(pApp->m_ppszHistory); ++i) {
      FREE(pApp->m_ppszHistory[i]);
   }
   
   // Release memory buffers
   FREE(pApp->m_pszFormData);    // FREE(NULL) is okay in BREW

   // Release other interfaces
   ReleaseObj((void**)&pApp->m_pFileMgr);
   ReleaseObj((void**)&pApp->m_pHTMLViewer);
   ReleaseObj((void**)&pApp->m_pINetMgr);
   ReleaseObj((void**)&pApp->m_pIWeb);
}

//*****************************************************************************
// EVENT HANDLING FUNCTIONS
//*****************************************************************************

/*===========================================================================

FUNCTION: ND_HandleEvent

DESCRIPTION:
	This is the main EventHandler for this app. All events to this app are handled in this
	function. All APPs must supply an Event Handler.  Key events are passed to specific
  event-handers depending on the current state of the application.

PARAMETERS:
	pi [in] - Pointer to the AEEApplet structure. This structure contains information specific
  	to this applet. It was initialized during the AEEClsCreateInstance() function.

	eCode [in] - Specifies the Event sent to this applet

  wParam [in] - Event specific data.

  dwParam [in] - Event specific data.

DEPENDENCIES:
  None

RETURN VALUE:
  TRUE - If the app has processed the event
  FALSE - If the app did not process the event

SIDE EFFECTS:
  None
===========================================================================*/

static boolean ND_HandleEvent(CNetDiagnosticsApp* pApp, AEEEvent eCode, uint16 wParam, uint32 dwParam )
{
   if (pApp->m_pIStatic && ISTATIC_HandleEvent(pApp->m_pIStatic, eCode, wParam, dwParam))
      return TRUE;
   if (IHTMLVIEWER_HandleEvent(pApp->m_pHTMLViewer, eCode, wParam, dwParam))
      return TRUE;

   switch(eCode)
   {
      case EVT_APP_START:
         ND_DisplaySplashScreen(pApp);
         return TRUE;

      case EVT_APP_STOP:
         return TRUE;

      case EVT_APP_SUSPEND:
         // Stop any current network or web access processes.  These functions
         // also cancel all appropriate callbacks.

         ND_EchoCleanup( pApp);
         ND_HTTPCleanup( pApp);

         // Cleanup all timers

         ISHELL_CancelTimer( pApp->a.m_pIShell, (PFNNOTIFY)ND_DisplaySplashScreenCB, pApp);

         // Set all controls to the inactive state

         if( pApp->m_pHTMLViewer )
            IHTMLVIEWER_SetActive( pApp->m_pHTMLViewer, FALSE );

         return TRUE;

      case EVT_APP_RESUME:
         IHTMLVIEWER_SetRect( pApp->m_pHTMLViewer, &pApp->m_rc );
         IHTMLVIEWER_SetActive( pApp->m_pHTMLViewer, TRUE );
         return TRUE;

      case EVT_KEY:
         switch (wParam)
         {
            case AVK_CLR:
               // call cleanup function
               if( pApp->m_cntHistory > 1 )
               {
                 ND_GoTo(pApp, NULL);
                 return TRUE;
               }
         }
         break;
   }
   return FALSE;
}

//*****************************************************************************
// HTML VIEWER NOTIFICATION CALLBACK FUNCTIONS
//****************************************************************************
static void ND_NotifyCB( void* pvUser, HViewNotify* pNotify )
{
   CNetDiagnosticsApp* pApp = (CNetDiagnosticsApp*) pvUser;

   switch( pNotify->code )
   {

   case HVN_REDRAW_SCREEN:
      IDISPLAY_ClearScreen(pApp->a.m_pIDisplay);
      IHTMLVIEWER_Redraw(pApp->m_pHTMLViewer);
      break;

   case HVN_JUMP:
   case HVN_SUBMIT:
      ND_GoTo(pApp, pNotify->u.jump.pszURL);
      break;

   case HVN_DONE:
      IHTMLVIEWER_SetRect(pApp->m_pHTMLViewer, &pApp->m_rc);
      IHTMLVIEWER_Redraw( pApp->m_pHTMLViewer );
      break;

   }
}



/*===========================================================================

FUNCTION: ND_GoTo

DESCRIPTION:

   Activate the appropriate view as described by the given URL.  This adds an
   entry onto the history list, or returns to the previous history entry.

PARAMETERS:
   pszURL = URL describing the view to activate and add to the history,
            or NULL to indicate that the previous history entry should be used

   pData [in] - The user data pointer that is passed as the only parameter
    to the callback.

DEPENDENCIES:
  None

RETURN VALUE:
  None

SIDE EFFECTS:
  Causes the phone display to be updated.
===========================================================================*/

static void ND_GoTo(CNetDiagnosticsApp *pApp, const char *pszURL)
{
   int pos = pApp->m_cntHistory;    // pos = number of hist entries = next history entry

   if (pszURL == NULL) {
      // go back
      if (pos < 2)
         return;
      --pos;
      pszURL = pApp->m_ppszHistory[pos-1];   // use 'top' entry
   } else {
      // add new entry to history
      if (pos >= MAX_HIST)
         return;
      ++pos;
      StrReplace(&pApp->m_ppszHistory[pos-1], pszURL);
   }

   // Cleanup previous state

   if (pApp->m_pfnViewCleanup) {
      pApp->m_pfnViewCleanup(pApp);
      pApp->m_pfnViewCleanup = NULL;
   }

   // Activate appropriate state
   
   pApp->m_cntHistory = pos;

   if (STRBEGINS("test:", pszURL)) {
      // read form data and begin test
      ND_StartTest(pApp, pszURL);
   } else /*if (STRBEGINS("file:", pszURL))*/ {
      // read and display file
      ND_DisplayMenu(pApp, pszURL);
   }
}


//*****************************************************************************
// DISPLAY MANIPULATION FUNCTIONS
//****************************************************************************

/*===========================================================================

FUNCTION: ND_DisplaySplashScreen

DESCRIPTION:
	This function clears the device screen and displays the application's 
  splash screen in the center of the display.  The function then sets a callback
  ND_DisplaySplashScreenCB to start the application's main menu screen.

  If the splash image fails to load, then just display the menu and 
  skip the callback.

PARAMETERS:
	pData [in] - The user data pointer that is passed as the only parameter
    to the callback.

DEPENDENCIES:
  None

RETURN VALUE:
  None

SIDE EFFECTS:
  Causes the phone display to be updated.
===========================================================================*/
static void ND_DisplaySplashScreenCB(CNetDiagnosticsApp* pApp)
{
   // Display main form
   ND_GoTo(pApp, JUMP_MAIN);
}

static void ND_DisplaySplashScreen(CNetDiagnosticsApp* pApp)
{
   IImage* pSplash = NULL;
   AEEImageInfo rImageInfo;

   if((pSplash = ISHELL_LoadResImage( pApp->a.m_pIShell, NETDIAGNOSTICS_RES_FILE, IDB_SPLASH)) != NULL)
   {
      // Get image information
      IIMAGE_GetInfo(pSplash, &rImageInfo);

      // Clear Device screen
      IDISPLAY_ClearScreen(pApp->a.m_pIDisplay);

      // Draw the image in the center of the screen
      IIMAGE_Draw(pSplash, (pApp->m_rc.dx/2) - (rImageInfo.cx/2), (pApp->m_rc.dy/2) - (rImageInfo.cy/2));

      // The image is no longer needed so release it
      IIMAGE_Release(pSplash);

      // Set the callback timer
      ISHELL_SetTimer( pApp->a.m_pIShell, SPLASH_TIMER_DURATION, (PFNNOTIFY)ND_DisplaySplashScreenCB, pApp);
    
      // Update Display
      IDISPLAY_Update(pApp->a.m_pIDisplay);

      return;
   }
   else
   {
     ND_GoTo( pApp, JUMP_MAIN );
   }
}

/*===========================================================================

FUNCTION: ND_DisplayMenu

DESCRIPTION:
	This function clears the device screen and displays the components of
  the application's Main Menu screen.  This consists of updating the 
  title bar text at the top of the screen and the HTML Viewer Control in the 
  remaining space on the screen.
  
  An HTML file is opened via the IFILE interface and its stream is passed
  to the HTML Viewer control which reads the text and displays it.  This text
  includes links which make up the user selectable options of the Main Menu.

PARAMETERS:
	pApp [in] - Pointer to the CNetDiagnosticsApp structure. This structure contains 
    information specific to this applet. 

DEPENDENCIES:
  Assumes the displayed controls have been previously created and 
  initialized.

RETURN VALUE:
  None

SIDE EFFECTS:
  Causes the phone display to be updated.
===========================================================================*/
static void ND_DisplayMenu(CNetDiagnosticsApp* pApp, const char *pszURL)
{
   IFile *pf;
   const char *pszFileName = pszURL;

   if (STRBEGINS("file:", pszFileName))
      pszFileName += STRLEN("file:");

   if (STRBEGINS("///", pszFileName))
      pszFileName += STRLEN("///");

   pf = IFILEMGR_OpenFile(pApp->m_pFileMgr, pszFileName, _OFM_READ);

   if (pf) {

      // Set the file from which the viewer will get its text
      IHTMLVIEWER_LoadStream( pApp->m_pHTMLViewer, (IAStream*)pf);

      // Release our reference to the file.  (The HTML viewer is responsible for
      // its own reference count while it uses the stream.)
      IFILE_Release(pf);

   } else {
      
      // Set the file from which the viewer will get its text
      IHTMLVIEWER_SetData( pApp->m_pHTMLViewer, "<h1>Error</h1>File Not Found", -1);

   }
}


/*===========================================================================

FUNCTION: ND_EchoCleanup

DESCRIPTION:
	Cleanup when exiting Network Access test state

PARAMETERS:
	pApp [in] - Pointer to the CNetDiagnosticsApp structure. This structure contains 
    information specific to this applet. 

DEPENDENCIES:

RETURN VALUE:
  None

SIDE EFFECTS:
  Causes the phone display to be updated.
===========================================================================*/
static void ND_EchoCleanup(CNetDiagnosticsApp *pApp)
{
   // Note: Socket is being deleted; otherwise we would cancel socket
   // callbacks.

   // Cancel DNS Callback
   CALLBACK_Cancel(&pApp->m_cb);

   // Release Socket
   ReleaseObj((void**)&pApp->m_pISocket);

   // Release Static Control
   ReleaseObj((void**)&pApp->m_pIStatic);
}


/*===========================================================================

FUNCTION: ND_HTTPCleanup

DESCRIPTION:
	Cleanup when exiting the HTTP test state.

PARAMETERS:
	pApp [in] - Pointer to the CNetDiagnosticsApp structure. This structure contains 
    information specific to this applet. 

DEPENDENCIES:
  Assumes the displayed controls have been previously created and 
  initialized.

RETURN VALUE:
  None

SIDE EFFECTS:
  Causes the phone display to be updated.
===========================================================================*/
static void ND_HTTPCleanup(CNetDiagnosticsApp* pApp)
{
   FOR_ALL_WEBACTIONS(pApp, p, WebAction_Stop(p));

   // Release Static Control
   ReleaseObj((void**)&pApp->m_pIStatic);
}


/*===========================================================================

FUNCTION: ND_StartTest

DESCRIPTION:
	Read results submitted from a form, and begin a test.

PARAMETERS:
	pApp [in] - Pointer to the CNetDiagnosticsApp structure. This structure contains 
              information specific to this applet.

   pszSubmit [in] - URL naming the test and including submitted form data.

              "test:echo?..." begins an echo test
              "test:http?..." begins an HTTP test

DEPENDENCIES:
  Assumes the displayed controls have been previously created and 
  initialized.

RETURN VALUE:
  None

SIDE EFFECTS:
  Causes the phone display to be updated.
===========================================================================*/
static void ND_StartTest(CNetDiagnosticsApp* pApp, const char* pszSubmit)
{
   AEERect rc;
   IStatic * pStatic = NULL;
   char *pszIter;

   // Reset values before reading form results

   pApp->m_bRS = pApp->m_bRT = pApp->m_bTCP = FALSE;
   pApp->m_pszHost = "";
   pApp->m_pszURL = "";
   pApp->m_pszMsg = "";

   // Duplicte form data so we can write over it
   
   StrReplace(&pApp->m_pszFormData, pszSubmit);

   // Locate, decode, and process form fields
   
   pszIter = pApp->m_pszFormData;

   if (!pszIter) {
      return;
   }

   pszIter = STRCHREND(pszIter, '?');
   if (*pszIter) {
      ++pszIter;
   }

   {
      IWebUtil     *piwu;
      WebFormField  wff;

      // go get me a gosh-darn parsin' machine
      if (SUCCESS != ISHELL_CreateInstance(pApp->a.m_pIShell, AEECLSID_WEBUTIL,
                                           (void **)&piwu)) {
         return;
      }
      
      // all pointers into a string we STRDUP'ed, all NULL terminated
      // by IWebUtil in place for me
      
      // IWebUtil can parse a bunch at a time, but we'll just iterate here

      while (0 != IWEBUTIL_ParseFormFields(piwu,&pszIter,&wff,1,0)) {
         boolean bOn = STREQ(wff.pcValue, "on");
         if (STREQ(wff.pcName, "HOST")) {
            pApp->m_pszHost = wff.pcValue;
         } else if (STREQ(wff.pcName, "URL")) {
            pApp->m_pszURL = wff.pcValue;
         } else if (STREQ(wff.pcName, "MSG")) {
            pApp->m_pszMsg = wff.pcValue;
         } else if (STREQ(wff.pcName, "RS")) {
            pApp->m_bRS = bOn;
         } else if (STREQ(wff.pcName, "RT")) {
            pApp->m_bRT = bOn;
         } else if (STREQ(wff.pcName, "PROTOCOL")) {
            if (STREQ(wff.pcValue, "1")) {
               pApp->m_bTCP = 1;
            }
         }
      }
      IWEBUTIL_Release(piwu);
      piwu = 0;
   }

   pApp->m_nDataLength = STRLEN(pApp->m_pszMsg);
   
   // Create Static Control to Display Messages
   if (ISHELL_CreateInstance(pApp->a.m_pIShell, AEECLSID_STATIC,(void**)&pApp->m_pIStatic) == AEE_SUCCESS)
      pStatic = pApp->m_pIStatic;   // Cache Pointer
   else
      return;

   ISTATIC_SetRect(pStatic, &pApp->m_rc);
   ISTATIC_SetProperties(pStatic, ST_ASCII|ST_NOSCROLL);

   // Disable Browser
   SETAEERECT(&rc, 0, 0, 0, 0);
   IHTMLVIEWER_SetRect(pApp->m_pHTMLViewer, &rc);

   // Clear Screen
   IDISPLAY_ClearScreen(pApp->a.m_pIDisplay);

   ND_Print(pApp, "** starting...\n");

   if (STRBEGINS(JUMP_ECHOTEST, pszSubmit)) {

      // Make note of type of cleanup to be performed when we exit this state   
      pApp->m_pfnViewCleanup = ND_EchoCleanup;
      
      // Start Echo Test
      Echoer_Start(pApp);

   } else {
      // Make note of type of cleanup to be performed when we exit this state   
      pApp->m_pfnViewCleanup = ND_HTTPCleanup;

      // Start HTTP test(s)
      FOR_ALL_WEBACTIONS(pApp, p, WebAction_Start(p, pApp->m_pszURL) );
   }
}

/*===========================================================================

FUNCTION: Echoer_Start

DESCRIPTION:
	Starts Echo Test

PARAMETERS:
	pApp [in] - Pointer to the CNetDiagnosticsApp structure. This structure contains 
    information specific to this applet. 

DEPENDENCIES:
  Assumes the displayed controls have been previously created and 
  initialized.

RETURN VALUE:
  None

SIDE EFFECTS:
  Causes the phone display to be updated.
===========================================================================*/
static void Echoer_Start(CNetDiagnosticsApp * pApp)
{
   // Reset statistics
   pApp->m_nTotalTime = pApp->m_nSendTime = pApp->m_nReceiveTime = 0;

   pApp->m_pISocket = INETMGR_OpenSocket(pApp->m_pINetMgr, (pApp->m_bTCP ? AEE_SOCK_STREAM : AEE_SOCK_DGRAM));

   if (pApp->m_pISocket == NULL) {
      ND_Print(pApp, "** OpenSocket Failed: Error %d\n", INETMGR_GetLastError(pApp->m_pINetMgr));
      return;
   }

   if (pApp->m_bTCP)
      Echoer_TCPConnect(pApp);
   else
      Echoer_UDPWrite(pApp);
}

/*===========================================================================

FUNCTION: Echoer_TCPConnected

DESCRIPTION:
	Callback for ISOCKET_Connect.

PARAMETERS:
	p [in] - Pointer to the CNetDiagnosticsApp structure. This structure contains 
    information specific to this applet.
   nErr - Error Code 

DEPENDENCIES:
   None

RETURN VALUE:
   None

SIDE EFFECTS:
   None
===========================================================================*/
static void Echoer_TCPConnected(void *p, int nErr)
{
   CNetDiagnosticsApp * pApp = (CNetDiagnosticsApp *)p;

   if ((nErr == AEE_NET_SUCCESS) || (nErr == AEE_NET_EISCONN)){
      pApp->m_nTCPIdx = 0;

      // Cache Write Start Time
      pApp->m_nSendTime = GETUPTIMEMS();

      Echoer_TCPWrite(pApp);    
   } else {
      ND_Print(pApp, "** Connect Failed: Error %d\n", nErr);
      ReleaseObj((void**)&pApp->m_pISocket);
   }
}

/*===========================================================================

FUNCTION: Echoer_TCPConnect

DESCRIPTION:
	Connects to a socket

PARAMETERS:
	pApp [in] - Pointer to the CNetDiagnosticsApp structure. This structure contains 
    information specific to this applet.

DEPENDENCIES:
   None

RETURN VALUE:
   None

SIDE EFFECTS:
   None
===========================================================================*/
static void Echoer_TCPConnect(CNetDiagnosticsApp * pApp)
{
   // GetHostByName() understands dot-notation IP addresses as well as domain
   // names so we don't need to call INET_ATON() separately (as on other
   // platforms).

   CALLBACK_Init(&pApp->m_cb,Echoer_TCPDNSConnect,pApp);
   INETMGR_GetHostByName(pApp->m_pINetMgr,&pApp->m_dnsr, pApp->m_pszHost,&pApp->m_cb);
}

/*===========================================================================

FUNCTION: Echoer_TCPDNSConnect

DESCRIPTION:
	Connects to a socket when IP address is to be resolved using DNS.

PARAMETERS:
	pApp [in] - Pointer to the CNetDiagnosticsApp structure. This structure contains 
    information specific to this applet.

DEPENDENCIES:
   None

RETURN VALUE:
   None

SIDE EFFECTS:
   None
===========================================================================*/
static void Echoer_TCPDNSConnect(void *p)
{
   int nErr;
   CNetDiagnosticsApp * pApp = (CNetDiagnosticsApp *)p;

   nErr = pApp->m_dnsr.nResult;

   if (nErr > AEEDNSMAXADDRS) {
      ND_Print(pApp, "** DNS Lookup Failed: Error %d\n", nErr);
      ReleaseObj((void**)&pApp->m_pISocket);
      return;
   }

   // Cache Start Time
   pApp->m_nTotalTime = GETUPTIMEMS();

   nErr = ISOCKET_Connect(pApp->m_pISocket, pApp->m_dnsr.addrs[0], 0x700, Echoer_TCPConnected, pApp);

   if (nErr != AEE_NET_SUCCESS) {
      ND_Print(pApp, "** Connect Failed: Error %d\n", ISOCKET_GetLastError(pApp->m_pISocket));
      ReleaseObj((void**)&pApp->m_pISocket);
   } else {
      ND_Print(pApp, "** connecting...\n");
   }
}
/*===========================================================================

FUNCTION: Echoer_TCPWrite

DESCRIPTION:
	Writes to a socket

PARAMETERS:
	pApp [in] - Pointer to the CNetDiagnosticsApp structure. This structure contains 
    information specific to this applet.

DEPENDENCIES:
   None

RETURN VALUE:
   None

SIDE EFFECTS:
   None
===========================================================================*/
static void Echoer_TCPWrite(CNetDiagnosticsApp * pApp)
{
   int         nSent;

   nSent = ISOCKET_Write(pApp->m_pISocket,((byte *)pApp->m_pszMsg) + pApp->m_nTCPIdx,
                         (uint16)(pApp->m_nDataLength - pApp->m_nTCPIdx));
   
   if (nSent == AEE_NET_ERROR) {
      ND_Print(pApp, "** Write Failed: Error %d\n", ISOCKET_GetLastError(pApp->m_pISocket));
      ReleaseObj((void**)&pApp->m_pISocket);
      return;
   }

   if (nSent != AEE_NET_WOULDBLOCK) {
      pApp->m_nTCPIdx += nSent;
      if (pApp->m_nTCPIdx >= pApp->m_nDataLength) {
         // Writing Complete
         // Calculate Send Time
         pApp->m_nSendTime = (GETUPTIMEMS() - pApp->m_nSendTime);
         // Reset Index
         pApp->m_nTCPIdx = 0;
         // Reset Buffer
         MEMSET(pApp->m_pszMsg, 0, pApp->m_nDataLength);
         // Display Message
         ND_Print(pApp, "** writing complete...\n");
         // Cache Read Time
         pApp->m_nReceiveTime = GETUPTIMEMS();
         // Start Reading
         Echoer_TCPRead(pApp);
         return;
      }
   }

   ND_Print(pApp, "** writing...\n");
   ISOCKET_Writeable(pApp->m_pISocket,(PFNNOTIFY)Echoer_TCPWrite,pApp);
}

/*===========================================================================

FUNCTION: Echoer_TCPRead

DESCRIPTION:
	Reads from a socket

PARAMETERS:
	pApp [in] - Pointer to the CNetDiagnosticsApp structure. This structure contains 
    information specific to this applet.

DEPENDENCIES:
   None

RETURN VALUE:
   None

SIDE EFFECTS:
   None
===========================================================================*/
static void Echoer_TCPRead(CNetDiagnosticsApp * pApp)
{
   int       nRead;

   nRead = ISOCKET_Read(pApp->m_pISocket, ((byte *)pApp->m_pszMsg) + pApp->m_nTCPIdx, 
                        pApp->m_nDataLength - pApp->m_nTCPIdx);
   
   if (nRead == AEE_NET_ERROR) {
      ND_Print(pApp, "** Read Failed: Error %d\n", ISOCKET_GetLastError(pApp->m_pISocket));
      ReleaseObj((void**)&pApp->m_pISocket);
      return;
   }

   if (nRead != AEE_NET_WOULDBLOCK) {
      pApp->m_nTCPIdx += nRead;

      if (pApp->m_nTCPIdx >= pApp->m_nDataLength) {
         // Calulate Receive Time
         pApp->m_nReceiveTime = (GETUPTIMEMS() - pApp->m_nReceiveTime);
         // Calculate Total Time
         pApp->m_nTotalTime = (GETUPTIMEMS() - pApp->m_nTotalTime);
         pApp->m_nTCPIdx = 0;
         ND_Print(pApp, "** reading Complete...\n");
         if (pApp->m_bRS) {
            ND_Print(pApp, "%s\n", pApp->m_pszMsg);
         }
         if (pApp->m_bRT) {
            ND_Print(pApp, ("Total Time: %d ms\n"
                            "Send Time: %d ms @ %d bps\n"
                            "Receive Time: %d ms @ %d bps\n"),
                     pApp->m_nTotalTime,
                     pApp->m_nSendTime, (pApp->m_nSendTime ? (pApp->m_nDataLength*1000)/pApp->m_nSendTime : 0),
                     pApp->m_nReceiveTime, (pApp->m_nReceiveTime ? (pApp->m_nDataLength*1000)/pApp->m_nReceiveTime : 0));
         }
         ReleaseObj((void**)&pApp->m_pISocket);
         return;
      }
   }
   ND_Print(pApp, "** reading...\n");
   ISOCKET_Readable(pApp->m_pISocket, (PFNNOTIFY)Echoer_TCPRead, pApp);
}

/*===========================================================================

FUNCTION: Echoer_UDPWrite

DESCRIPTION:
	Send UDP data to a socket

PARAMETERS:
	pApp [in] - Pointer to the CNetDiagnosticsApp structure. This structure contains 
    information specific to this applet.

DEPENDENCIES:
   None

RETURN VALUE:
   None

SIDE EFFECTS:
   None
===========================================================================*/
static void Echoer_UDPWrite(CNetDiagnosticsApp * pApp)
{
   // GetHostByName() understands dot-notation IP addresses as well as domain
   // names so we don't need to call INET_ATON() separately (as on other
   // platforms).
   
   CALLBACK_Init(&pApp->m_cb,Echoer_UDPDNSWrite,pApp);
   INETMGR_GetHostByName(pApp->m_pINetMgr,&pApp->m_dnsr, pApp->m_pszHost,&pApp->m_cb);
}

/*===========================================================================

FUNCTION: Echoer_UDPDNSWrite

DESCRIPTION:
	Send UDP packet to a socket when IP address is to be resolved using DNS.

PARAMETERS:
	pApp [in] - Pointer to the CNetDiagnosticsApp structure. This structure contains 
    information specific to this applet.

DEPENDENCIES:
   None

RETURN VALUE:
   None

SIDE EFFECTS:
   None
===========================================================================*/
static void Echoer_UDPDNSWrite(void *p)
{
   int            nErr;
   CNetDiagnosticsApp * pApp = (CNetDiagnosticsApp *)p;

   nErr = pApp->m_dnsr.nResult;

   if (nErr > AEEDNSMAXADDRS) {
      ND_Print(pApp, "** DNS Lookup Failed: Error %d\n", nErr);
      ReleaseObj((void**)&pApp->m_pISocket);
      return;
   }

   // Cache Start Time
   pApp->m_nTotalTime = GETUPTIMEMS();

   nErr = ISOCKET_SendTo(pApp->m_pISocket, (byte*)pApp->m_pszMsg, pApp->m_nDataLength, 0, 
                          pApp->m_dnsr.addrs[0], HTONS(IPPORT_ECHO));

   if (nErr == AEE_NET_WOULDBLOCK) {
      ND_Print(pApp,  "** sending...\n");
      ISOCKET_Writeable(pApp->m_pISocket, (PFNNOTIFY)Echoer_UDPWrite, pApp);
   } else if (nErr == AEE_NET_ERROR) {
      ND_Print(pApp, "Send Failed: Error %d\n", ISOCKET_GetLastError(pApp->m_pISocket));
      ReleaseObj((void**)&pApp->m_pISocket);
   } else {         
      ND_Print(pApp,  "** sending complete...\n");
      // Reset Buffer
      MEMSET(pApp->m_pszMsg, 0, pApp->m_nDataLength);
      Echoer_UDPRead(pApp);
   }
}
/*===========================================================================

FUNCTION: Echoer_UDPRead

DESCRIPTION:
	Receives UDP data from a socket

PARAMETERS:
	pApp [in] - Pointer to the CNetDiagnosticsApp structure. This structure contains 
    information specific to this applet.

DEPENDENCIES:
   None

RETURN VALUE:
   None

SIDE EFFECTS:
   None
===========================================================================*/
static void Echoer_UDPRead(CNetDiagnosticsApp * pApp)
{
   int         nRead;

   nRead = ISOCKET_RecvFrom(pApp->m_pISocket, (byte *)pApp->m_pszMsg, 
                            pApp->m_nDataLength,0,0,0);

   if (nRead == AEE_NET_ERROR) {
      ND_Print(pApp, "Receive Failed: Error %d\n", ISOCKET_GetLastError(pApp->m_pISocket));
      ReleaseObj((void**)&pApp->m_pISocket);
   } else {
      if (nRead == AEE_NET_WOULDBLOCK) {
         ND_Print(pApp, "** receiving...\n");
         ISOCKET_Readable(pApp->m_pISocket, (PFNNOTIFY)Echoer_UDPRead, pApp);
      } else {
         // Calculate Total Time
         pApp->m_nTotalTime = (GETUPTIMEMS() - pApp->m_nTotalTime);
         ND_Print(pApp, "** receiving complete...\n");
         if (pApp->m_bRS) {
            ND_Print(pApp, "%s\n", pApp->m_pszMsg);
         }
         if (pApp->m_bRT) {
            ND_Print(pApp, "Total Time: %d ms", pApp->m_nTotalTime);
         }
         ReleaseObj((void**)&pApp->m_pISocket);
      }
   }
}


/*===========================================================================

FUNCTION: WebAction_Start

DESCRIPTION:
   Kicks off a web transaction

PARAMETERS:
   pwa: wrapper for the web transaction
   pszUrl: the URL to go GET/POST

DEPENDENCIES:
   None

RETURN VALUE:
   None

SIDE EFFECTS:
   None
===========================================================================*/
static void WebAction_Start(WebAction *pwa, char *pszUrl)
{
   ISourceUtil *pisu;
   CNetDiagnosticsApp * pApp = pwa->pParent;

   // look to see if there's POST data, this is totally non-standard, but 
   // easy to put into tests
   pwa->pszPostData = STRCHR(pszUrl, 1);
   
   // if there's post data, construct a stream for IWeb to consume
   if ((char *)0 != pwa->pszPostData) {
      *pwa->pszPostData = 0;
      if (SUCCESS == 
          ISHELL_CreateInstance(pApp->a.m_pIShell,AEECLSID_SOURCEUTIL, 
                                (void **)&pisu)) {
         
         ISOURCEUTIL_PeekFromMemory(pisu, pwa->pszPostData + 1, 
                                    STRLEN(pwa->pszPostData + 1), 0, 0,
                                    &pwa->pipPostData);
         ISOURCEUTIL_Release(pisu);
      }
   }

   // initialize the callback, where I'll be called when the request
   // completes
   CALLBACK_Init(&pwa->cb, WebAction_GotResp, pwa);
   
   pwa->uStart = GETUPTIMEMS();
   // start transaction, pass callbacks for web status, web headers
   // the extra WEBOPT_HEADER is used to help test what's sent 
   // (snoop.sh above shows all the headers)
   if ((IPeek *)0 != pwa->pipPostData) {
      IWEB_GetResponse(pApp->m_pIWeb,
                       (pApp->m_pIWeb, &pwa->piWResp, &pwa->cb, pszUrl, 
                        WEBOPT_HANDLERDATA, pwa,
                        WEBOPT_HEADER, "X-Method: POST\r\n", /* for kicks */
                        WEBOPT_HEADERHANDLER, WebAction_Header, 
                        WEBOPT_STATUSHANDLER, WebAction_Status, 
                        WEBOPT_METHOD, "POST",
                        WEBOPT_BODY, pwa->pipPostData,
                        WEBOPT_CONTENTLENGTH, STRLEN(pwa->pszPostData + 1),
                        WEBOPT_END));
   } else {
      IWEB_GetResponse(pApp->m_pIWeb,
                       (pApp->m_pIWeb, &pwa->piWResp, &pwa->cb, pszUrl, 
                        WEBOPT_HANDLERDATA, pwa, 
                        WEBOPT_HEADER, "X-Method: GET\r\n",
                        WEBOPT_HEADERHANDLER, WebAction_Header, 
                        WEBOPT_STATUSHANDLER, WebAction_Status, 
                        WEBOPT_END));
   }
}

/*===========================================================================

FUNCTION: WebAction_GotResp

DESCRIPTION:
   Web transaction callback, fires when the response body is ready to
   be read, or when an error has occurred

PARAMETERS:
   p: the WebAction

DEPENDENCIES:
   None

RETURN VALUE:
   None

SIDE EFFECTS:
   None
===========================================================================*/
static void WebAction_GotResp(void *p)
{
   WebAction *pwa = (WebAction *)p;
   CNetDiagnosticsApp * pApp = pwa->pParent; 
   WebRespInfo *pwri;

   // get information about how the web transaction went
   // pwa->piWResp is ***NEVER NULL***, even though the transaction may fail
   // for wont of memory
   pwri = IWEBRESP_GetInfo(pwa->piWResp);

   ND_Print(pApp, "** got response...\n** info code: %d\n", pwri->nCode);

   // body may be NULL
   if ((ISource *)0 != pwri->pisMessage) {
      ISourceUtil *pisu;

      // read the body by creating an IGetLine to help parse into lines
         
      // This step is not actually necessary if you don't need line-by-line
      // output.  In that case, use pisMessage directly.
      ISHELL_CreateInstance(pApp->a.m_pIShell,AEECLSID_SOURCEUTIL,
                            (void **)&pisu);
      
      if ((ISourceUtil *)0 != pisu) {
         ISOURCEUTIL_GetLineFromSource(pisu, pwri->pisMessage, 1100, 
                                       &pwa->piGetLine);
         ISOURCEUTIL_Release(pisu);
      }
   }
   
   if ((IGetLine *)0 != pwa->piGetLine) {

      pwa->uRecvStart = GETUPTIMEMS(); // record that I started reading
      
      // got an IGetLine, go ahead and read it
      pwa->nBytes = 0;
      WebAction_ReadLines(pwa);

   } else {
      // ug, tranaction failed or couldn't get an IGetLine, oh well, bail
      ND_Print(pApp, "** no response\n");
      WebAction_Stop(pwa); // stop cleans up
   }
   
}

/*===========================================================================

FUNCTION: WebAction_Header

DESCRIPTION:
   Received header callback for a web transaction. cpszName is NULL in the case 
   of continuation header line parts.

PARAMETERS:
   p: a WebAction (the subscriber)
   cpszName: the name of the web header (like "Content-Type")
   pglVal: the value of the header, like "text/html"

DEPENDENCIES:
   None

RETURN VALUE:
   None

SIDE EFFECTS:
   None
===========================================================================*/
static void WebAction_Header(void *p, const char *cpszName, GetLine *pglVal)
{
   WebAction *pwa = (WebAction *)p;
   CNetDiagnosticsApp * pApp = pwa->pParent;
  
   if (pApp->m_bRS) { // If response is to be displayed
      if ((char *)0 != cpszName) {
         ND_Print(pApp, "%s:", cpszName);
      }
      ND_Print(pApp, "%s\n", pglVal->psz);
   }
}

/*===========================================================================

FUNCTION: WebAction_Status

DESCRIPTION:
   Web status callback for a Web transaction 

PARAMETERS:
   p: a WebAction (the subscriber)
   ws: type of status
   pVal: unused as of yet

DEPENDENCIES:
   None

RETURN VALUE:
   None

SIDE EFFECTS:
   None
===========================================================================*/
static void WebAction_Status(void *p, WebStatus ws, void *pVal)
{
   char *      pszStatus = 0;
   WebAction * pwa = (WebAction *)p;
   
   (void)pVal;

   switch (ws) {
   case WEBS_CANCELLED:
      pszStatus = "** cancelled...\n";
      break;
   case WEBS_GETHOSTBYNAME:
      pszStatus = "** finding host...\n";
      break;          
   case WEBS_CONNECT:
      pszStatus = "** connecting...\n";
      break;
   case WEBS_SENDREQUEST:
      pszStatus = "** sending...\n";
      break;
   case WEBS_READRESPONSE:
      pszStatus = "** receiving...\n";
      break;
   case WEBS_GOTREDIRECT:
      pszStatus = "** redirect...\n";
      break;
   case WEBS_CACHEHIT:
      pszStatus = "** cache hit...\n";
      break;
   }

   // show that status!
   if ((char *)0 != pszStatus) {
      ND_Print(pwa->pParent, "%s", pszStatus);
   }
}

/*===========================================================================

FUNCTION: WebAction_Stop

DESCRIPTION:
   Halts a web transaction, wrapped/represented by a WebAction

PARAMETERS:
   pwa: the WebAction

DEPENDENCIES:
   None

RETURN VALUE:
   None

SIDE EFFECTS:
   None
===========================================================================*/
static void WebAction_Stop(WebAction *pwa)
{
   // this cancels any pending web transaction, or readable on the 
   // response body.  if nothing is pending, this has no effect
   CALLBACK_Cancel(&pwa->cb);
   
   // then clean up, if necessary
   if ((char *)0 != pwa->pszPostData) {
      *pwa->pszPostData = 1; // write delimiter back in, if any
      pwa->pszPostData = 0;
   }

   ReleaseObj((void **)&pwa->pipPostData);
   ReleaseObj((void **)&pwa->piGetLine);  // let body go
   ReleaseObj((void **)&pwa->piWResp);    // let response go
}


/*===========================================================================

FUNCTION: WebAction_ReadLines

DESCRIPTION:
   consumes the body of a web response, line by line, is called when the
   IGetLine that represents the body is readable.

PARAMETERS:
   pwa: wrapper for the web transaction
   pszUrl: the URL to go GET/POST

DEPENDENCIES:
   None

RETURN VALUE:
   None

SIDE EFFECTS:
   None
===========================================================================*/
static void WebAction_ReadLines(void *p)
{
   int        rv;
   GetLine    gl;
   WebAction *pwa = (WebAction *)p;
   CNetDiagnosticsApp * pApp = pwa->pParent;

   // read a line
   rv = IGETLINE_GetLine(pwa->piGetLine, &gl, IGETLINE_LF);
   if (rv == IGETLINE_WAIT) {
      CALLBACK_Init(&pwa->cb, WebAction_ReadLines, pwa);
      IGETLINE_Peekable(pwa->piGetLine, &pwa->cb);
      return;
   }

   // process line

   pwa->nBytes += gl.nLen + IGETLINE_EOLSize(rv);
      
   if (pApp->m_bRS) {
      // show all data
      ND_Print(pApp, "%s\n", gl.psz, NULL, TRUE);
   } else {
      // show one hash mark per line (truncated or otherwise)
      ND_Print(pApp, "#");
   }

   // if stream has not reached EOF or ERROR
   if (!IGETLINE_Exhausted(rv)) {
      // wait for more data
      CALLBACK_Init(&pwa->cb, WebAction_ReadLines, pwa);
      IGETLINE_Readable(pwa->piGetLine, &pwa->cb);
      return;
   }

   // no more data
   
   if (rv == IGETLINE_ERROR) {
      ND_Print(pApp, "\n** error\n");
   } else {
      ND_Print(pApp, "\n** end\n");
   }
   
   if (pApp->m_bRT) {
      uint32 uNow = GETUPTIMEMS();
      ND_Print(pApp,
               "** %d bytes\n"
               "** %dB/s\n"
               "** %dms et (recv)\n"
               "** %dms et (total)\n",
               pwa->nBytes, 
               pwa->nBytes * 1000 / MAX(1,uNow - pwa->uRecvStart),
               uNow - pwa->uRecvStart,
               uNow - pwa->uStart);
   }

   // am done, _Stop cleans up
   WebAction_Stop(pwa);
}

