/***********************************************************************
 * 
 *  Copyright (C) 2006 Novell, Inc. All Rights Reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, Novell, Inc.
 * 
 *  To contact Novell about this file by physical or electronic mail, 
 *  you may find current contact information at www.novell.com.
 * 
 *  Author: Juan Carlos Luciani <jluciani@novell.com>
 *
 ***********************************************************************/

#include <windows.h>
#include "casa_c_authtoken.h"

// Globals
char usageString[] = "usage: test -a serverAddress -p serverPort [-h]\n";

char     *pServerAddress = NULL;
int      serverPort = 0;
BOOLEAN  execHttpTest = FALSE;


/***********************************************************************
 *
 * dtoul()
 *
 ***********************************************************************/
int
dtoul(
   IN    char *cp,
   IN    int len)
{
   int   n = 0;
   int   i;

   for (i = 0; i < len; i++, cp++)
   {
      // Verify that we are dealing with a valid digit
      if (*cp >= '0' && *cp <= '9')
      {
         n = 10 * n + (*cp - '0');
      }
      else
      {
         printf("-dtoul- Found invalid digit\n");
         break;
      }
   }
      
   return n;
}


/***********************************************************************
 *
 * EncodeData()
 *
 ***********************************************************************/
int
EncodeData(
   IN    const void *pData,
   IN    const int32_t dataLen,
   INOUT char **ppEncodedData,
   INOUT int32_t *pEncodedDataLen)
{
   int8_t      base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
   int         retStatus;
   int         encodedSize;

   char        *pTmp;

   // Determine the encoded size and allocate a buffer to hold the encoded data
   encodedSize = ((dataLen * 4 + 2) / 3) - (dataLen % 3 ) + 4;
   pTmp = (char*) malloc(encodedSize);
   *ppEncodedData = pTmp;
   if (*ppEncodedData)
   {
      uint8_t  *pOut, *pIn;
      int      i;

      // Setup pointers to move through the buffers
      pIn = (uint8_t*) pData;
      pOut = (uint8_t*) *ppEncodedData;

      // Perform the encoding
      for (i = 0; i < dataLen - 2; i += 3)
      {
          *pOut++ = base64[(pIn[i] >> 2) & 0x3F];
          *pOut++ = base64[((pIn[i] & 0x3) << 4) |
                          ((int32_t)(pIn[i + 1] & 0xF0) >> 4)];
          *pOut++ = base64[((pIn[i + 1] & 0xF) << 2) |
                          ((int32_t)(pIn[i + 2] & 0xC0) >> 6)];
          *pOut++ = base64[pIn[i + 2] & 0x3F];
      }
      if (i < dataLen)
      {
          *pOut++ = base64[(pIn[i] >> 2) & 0x3F];
          if (i == (dataLen - 1))
          {
              *pOut++ = base64[((pIn[i] & 0x3) << 4)];
              *pOut++ = '=';
          }
          else
          {
              *pOut++ = base64[((pIn[i] & 0x3) << 4) |
                              ((int32_t)(pIn[i + 1] & 0xF0) >> 4)];
              *pOut++ = base64[((pIn[i + 1] & 0xF) << 2)];
          }
          *pOut++ = '=';
      }
      *pOut++ = '\0';

      // Return the encoded data length
      *pEncodedDataLen = (int32_t)(pOut - (uint8_t*)*ppEncodedData); 

      // Success
      retStatus = 0;
   }
   else
   {
      printf("-EncodeData- Buffer allocation failure\n");
      retStatus = -1;
   }

   return retStatus;
}


/***********************************************************************
 *
 * NonHttpTest()
 *
 ***********************************************************************/
void NonHttpTest(void)
{
   CasaStatus  retStatus;
   char        authToken[4096];
   int         authTokenLen = sizeof(authToken);

   // Obtain an authentication token for the testService
   retStatus = ObtainAuthToken("testService", pServerAddress, authToken, &authTokenLen);
   if (!CASA_SUCCESS(retStatus))
   {
      printf("-NonHttpTest- ObtainAuthToken failed with status %d\n", retStatus);
   }
   else
   {
      SOCKET               sock;
      struct sockaddr_in   localAddr = {0};
      struct sockaddr_in   remoteAddr = {0};
      struct linger        linger_opt = {1, 15};
      struct hostent       *pLookupResult;
      int                  winsockStartupResult;
      WSADATA              winsockData;

      printf("-NonHttpTest- ObtainAuthToken succedded, tokenlen = %d\n", authTokenLen);

      // Send the token to the server
      //
      // First initialize winsock
      if ((winsockStartupResult = WSAStartup(MAKEWORD(2,2), &winsockData)) == 0)
      {
         // Open socket
         sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
         if (sock != INVALID_SOCKET)
         {
            // Setup the local address structure
            localAddr.sin_family = AF_INET;
            localAddr.sin_addr.s_addr = htonl(INADDR_ANY);

            // Bind socket
            if (!bind(sock, (const struct sockaddr*) &localAddr, sizeof(struct sockaddr_in)))
            {
               // Resolve the server address
               pLookupResult = gethostbyname(pServerAddress);
               if (pLookupResult)
               {
                  // Validate the address type returned
                  if (pLookupResult->h_addrtype == AF_INET)
                  {
                     int   numAddressesFound = 0;

                     // Determine how many addresses where returned
                     while (pLookupResult->h_addr_list[numAddressesFound] != NULL)
                     {
                        //printf("ServerAddress = %08X\n", *((int*) pLookupResult->h_addr_list[numAddressesFound]));
                        numAddressesFound ++;
                     }
                     //printf("Found %d addresses\n", numAddressesFound);

                     // Setup the remote address structure with the lookup results
                     remoteAddr.sin_family = AF_INET;
                     remoteAddr.sin_port = serverPort;
                     remoteAddr.sin_addr.s_addr = *((int*) pLookupResult->h_addr_list[0]); // Short-cut
                     //printf("ServerAddress = %08X\n", remoteAddr.sin_addr.s_addr);

                     // Perform connect operation
                     if (connect(sock,
                                 (struct sockaddr*) &remoteAddr,
                                 sizeof(struct sockaddr_in)) == SOCKET_ERROR)
                     {
                        printf("-NonHttpTest- Connection creation failed, error = %d\n", WSAGetLastError());
                     }
                     else
                     {
                        // Now the connection is setup, send the credentials to the server as one line.
                        // using our cheesy protocol followed by a hello string.
                        //
                        // Send the token to the server (including NULL terminator)
                        send(sock, authToken, (int) strlen(authToken) + 1, 0);

                        // Send new line
                        send(sock, "\n", 1, 0);

                        // Send "hello"
                        //send(sock, helloString, strlen(helloString) + 1, MSG_NOSIGNAL);

                        // Send new line
                        //send(sock, "\n", 1, 0);

                        // Shutdown the connection
                        shutdown(sock, 0);
                     }
                  }
                  else
                  {
                     printf("-NonHttpTest- Unsupported address type returned %08X\n", pLookupResult->h_addrtype);
                  }
               }
               else
               {
                  printf("-NonHttpTest- Lookup for %s failed\n", pServerAddress);
               }
            }
            else
            {
               printf("-NonHttpTest- Unable to bind socket, error = %d", errno);
            }

            // Close the socket
            setsockopt(sock, SOL_SOCKET, SO_LINGER, (const char*) &linger_opt, sizeof(linger_opt));
            closesocket(sock);
         }
         else
         {
            printf("-NonHttpTest- Unable to open socket, error = %d\n", errno);
         }

         // Close winsock
         WSACleanup();
      }
      else
      {
         printf("-NonHttpTest- WSAStartup failed, error = %d\n", winsockStartupResult);
      }
   }
}


/***********************************************************************
 *
 * HttpTest()
 *
 ***********************************************************************/
void HttpTest(void)
{
   CasaStatus  retStatus;
   char        authToken[4096];
   int         authTokenLen = sizeof(authToken);

   // Obtain an authentication token for the testService
   retStatus = ObtainAuthToken("testService", pServerAddress, authToken, &authTokenLen);
   if (!CASA_SUCCESS(retStatus))
   {
      printf("-HttpTest- ObtainAuthToken failed with status %d\n", retStatus);
   }
   else
   {
      SOCKET               sock;
      struct sockaddr_in   localAddr = {0};
      struct sockaddr_in   remoteAddr = {0};
      struct linger        linger_opt = {1, 15};
      struct hostent       *pLookupResult;
      int                  winsockStartupResult;
      WSADATA              winsockData;

      //printf("ObtainAuthToken succedded, token = %s\n", authToken);
      printf("-HttpTest- ObtainAuthToken succedded, tokenlen = %d\n", authTokenLen);

      // Send the token to the server
      //
      // First initialize winsock
      if ((winsockStartupResult = WSAStartup(MAKEWORD(2,2), &winsockData)) == 0)
      {
         // Open socket
         sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
         if (sock != INVALID_SOCKET)
         {
            // Setup the local address structure
            localAddr.sin_family = AF_INET;
            localAddr.sin_addr.s_addr = htonl(INADDR_ANY);

            // Bind socket
            if (!bind(sock, (const struct sockaddr*) &localAddr, sizeof(struct sockaddr_in)))
            {
               // Resolve the server address
               pLookupResult = gethostbyname(pServerAddress);
               if (pLookupResult)
               {
                  // Validate the address type returned
                  if (pLookupResult->h_addrtype == AF_INET)
                  {
                     int   numAddressesFound = 0;

                     // Determine how many addresses where returned
                     while (pLookupResult->h_addr_list[numAddressesFound] != NULL)
                     {
                        //printf("ServerAddress = %08X\n", *((int*) pLookupResult->h_addr_list[numAddressesFound]));
                        numAddressesFound ++;
                     }
                     //printf("Found %d addresses\n", numAddressesFound);


                     // Setup the remote address structure with the lookup results
                     remoteAddr.sin_family = AF_INET;
                     remoteAddr.sin_port = serverPort;
                     remoteAddr.sin_addr.s_addr = *((int*) pLookupResult->h_addr_list[0]); // Short-cut
                     //printf("ServerAddress = %08X\n", remoteAddr.sin_addr.s_addr);

                     // Perform connect operation
                     if (connect(sock,
                                 (struct sockaddr*) &remoteAddr,
                                 sizeof(struct sockaddr_in)) == SOCKET_ERROR)
                     {
                        printf("-HttpTest- Connection creation failed, error = %d\n", WSAGetLastError());
                     }
                     else
                     {
                        char *pBasicCredentials;
                        char *pEncodedBasicCredentials;
                        int  encodedLength;
                        char CasaPrincipal[] = "CasaPrincipal:";
                        char HTTPReqPart1[] = "GET /example-info HTTP/1.1\r\\nUser-Agent: CasaTestClient\r\nHost: jcstation.dnsdhcp.provo.novell.com:4096\r\nConnection: Keep-Alive\r\nAuthorization: Basic ";

                        // Now the connection is setup, send 1st part of HTTP request to the server.
                        send(sock, HTTPReqPart1, (int) strlen(HTTPReqPart1), 0);

                        // Now setup the HTTP Basic Credentials
                        pBasicCredentials = (char*) malloc(strlen(CasaPrincipal) + strlen(authToken) + 1);
                        if (pBasicCredentials)
                        {
                           char *pEncodedCredentials;

                           memcpy(pBasicCredentials, CasaPrincipal, sizeof(CasaPrincipal));
                           strcat(pBasicCredentials, authToken);

                           // Now Base64 encode the credentials
                           if (EncodeData(pBasicCredentials, strlen(pBasicCredentials), &pEncodedBasicCredentials, &encodedLength) == 0)
                           {
                              // Send the encoded credentials
                              send(sock, pEncodedBasicCredentials, encodedLength - 1, 0);

                              // Send the rest of the header
                              send(sock, "\r\n\r\n", 4, 0);

                              // Free the buffer holding the encoded credentials
                              free(pEncodedBasicCredentials);
                           }
                           else
                           {
                              printf("-HttpTest- Error encoding credentials\n");
                           }

                           // Free the buffer containing the basic credentials
                           free(pBasicCredentials);
                        }
                        else
                        {
                           printf("-HttpTest- Buffer allocation failure\n");
                        }

                        // Shutdown the connection
                        shutdown(sock, 0);
                     }
                  }
                  else
                  {
                     printf("-HttpTest- Unsupported address type returned %08X\n", pLookupResult->h_addrtype);
                  }
               }
               else
               {
                  printf("-HttpTest- Lookup for %s failed\n", pServerAddress);
               }
            }
            else
            {
               printf("-HttpTest- Unable to bind socket, error = %d", errno);
            }

            // Close the socket
            setsockopt(sock, SOL_SOCKET, SO_LINGER, (const char*) &linger_opt, sizeof(linger_opt));
            closesocket(sock);
         }
         else
         {
            printf("-HttpTest- Unable to open socket, error = %d\n", errno);
         }

         // Close winsock
         WSACleanup();
      }
      else
      {
         printf("-HttpTest- WSAStartup failed, error = %d\n", winsockStartupResult);
      }
   }
}


/***********************************************************************
 *
 * main()
 *
 ***********************************************************************/
int main(int argc, char* argv[])
{
   // Process input parameters
   int i = 1;
   while(argv[i] != NULL)
   {
      if (stricmp(argv[i], "-a") == 0)
      {
         // Server Address option, the next argument should
         // contain the address.
         i++;
         if (argv[i] != NULL)
         {
            pServerAddress = argv[i];
         }
         else
         {
            printf(usageString);
            return -1;
         }
      }
      else if (stricmp(argv[i], "-p") == 0)
      {
         // Server port option, the next argument should
         // contain the port.
         i++;
         if (argv[i] != NULL)
         {
            serverPort = htons(dtoul(argv[i], strlen(argv[i])));
         }
         else
         {
            printf(usageString);
            return -1;
         }
      }
      else if (stricmp(argv[i], "-h") == 0)
      {
         // Perform http test option
         execHttpTest = TRUE;
      }

      // Advance to the next argument
      i++;
   }

   // Verify that the server address and port were specified
   if (pServerAddress && serverPort != 0)
   {
      // Repeat the test when indicated
      printf("Press 'Enter' to run test or 'n + Enter' to stop.\n");
      while(getchar() != 'n')
      {
         // Execute the appropriate test
         if (execHttpTest)
         {
            HttpTest();
         }
         else
         {
            NonHttpTest();
         }
         printf("Press 'Enter' to run test or 'n + Enter' to stop.\n");
      }
   }
   else
   {
      printf(usageString);
      return -1;
   }

   return 0;
}