/***********************************************************************
 * 
 *  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 "platform.h"

// Externals
extern
char     *pServerAddress;

extern
int      serverPort;

extern
bool     execHttpTest;

extern
char     serviceName[];

extern
char     *pServiceName;


/***********************************************************************
 *
 * 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        *pAuthToken;
   int         authTokenLen = 0;

   // First call to get the authentication token with no output buffer so
   // that we can determine the buffer size necessary to hold the token.
   retStatus = ObtainAuthToken(pServiceName, pServerAddress, NULL, &authTokenLen);
   if (CasaStatusCode(retStatus) == CASA_STATUS_BUFFER_OVERFLOW)
   {
      // Allocate buffer to receive the token
      pAuthToken = (char*) malloc(authTokenLen);
      if (pAuthToken)
      {
         // Now get the token
         retStatus = ObtainAuthToken(pServiceName, pServerAddress, pAuthToken, &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;

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

            // Send the token to the server
            //
            // 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", errno);
                        }
                        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, pAuthToken, (int) strlen(pAuthToken) + 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);
            }
         }
         
         // Release the buffer allocated for the token
         free(pAuthToken);
      }
      else
      {
         printf("-NonHttpTest- Failed to allocate buffer for token\n", 0);
      }
   }
   else
   {
      printf("-NonHttpTest- ObtainAuthToken failed with status %0X\n", retStatus);
   }
}


/***********************************************************************
 *
 * HttpTest()
 *
 ***********************************************************************/
void HttpTest(void)
{
   CasaStatus  retStatus;
   char        *pAuthToken;
   int         authTokenLen = 0;

   // First call to get the authentication token with no output buffer so
   // that we can determine the buffer size necessary to hold the token.
   retStatus = ObtainAuthToken(pServiceName, pServerAddress, NULL, &authTokenLen);
   if (CasaStatusCode(retStatus) == CASA_STATUS_BUFFER_OVERFLOW)
   {
      // Allocate buffer to receive the token
      pAuthToken = (char*) malloc(authTokenLen);
      if (pAuthToken)
      {
         // Now get the token
         retStatus = ObtainAuthToken(pServiceName, pServerAddress, pAuthToken, &authTokenLen);
         if (!CASA_SUCCESS(retStatus))
         {
            printf("-HttpTest- ObtainAuthToken failed with status %0X\n", retStatus);
         }
         else
         {
            SOCKET               sock;
            struct sockaddr_in   localAddr = {0};
            struct sockaddr_in   remoteAddr = {0};
            struct linger        linger_opt = {1, 15};
            struct hostent       *pLookupResult;

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

            // Send the token to the server
            // 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", errno);
                        }
                        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(pAuthToken) + 1);
                           if (pBasicCredentials)
                           {
                              memcpy(pBasicCredentials, CasaPrincipal, sizeof(CasaPrincipal));
                              strcat(pBasicCredentials, pAuthToken);

                              // Now Base64 encode the credentials
                              if (EncodeData((const void*) pBasicCredentials,
                                             (const int32_t) strlen(pBasicCredentials),
                                             &pEncodedBasicCredentials,
                                             (int32_t *) &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);
            }
         }

         // Release the buffer allocated for the token
         free(pAuthToken);
      }
      else
      {
         printf("-HttpTest- Failed to allocate buffer for token\n", 0);
      }
   }
   else
   {
      printf("-HttpTest- ObtainAuthToken failed with status %0X\n", retStatus);
   }
}