/*
 *  This file is part of the KDE libraries
 *  Copyright (C) 2001 Thiago Macieira <thiago.macieira@kdemail.net>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  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 General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 **/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

#include <tqptrlist.h>
#include <tqstring.h>

#include <tdeuniqueapplication.h>
#include <ksocks.h>
#include <ksockaddr.h>
#include <kextsock.h>
#include <tdeaboutdata.h>
#include <tdecmdlineargs.h>

#include "netsupp.h"

/*
 * These constants tell the flags in KDE::resolverFlags
 * This is copied from ../netsupp.cpp
 */
#define KRF_KNOWS_AF_INET6		0x01	/* if present, the code knows about AF_INET6 */
#define KRF_USING_OWN_GETADDRINFO	0x02	/* if present, we are using our own getaddrinfo */
#define KRF_USING_OWN_INET_NTOP		0x04	/* if present, we are using our own inet_ntop */
#define KRF_USING_OWN_INET_PTON		0x08	/* if present, we are using our own inet_pton */
#define KRF_CAN_RESOLVE_UNIX		0x100	/* if present, the resolver can resolve Unix sockets */
#define KRF_CAN_RESOLVE_IPV4		0x200	/* if present, the resolver can resolve to IPv4 */
#define KRF_CAN_RESOLVE_IPV6		0x400	/* if present, the resolver can resolve to IPv6 */

namespace KDE
{
  extern const int resolverFlags;
}

class TestApp : public TDEUniqueApplication
{
public:
  TestApp() :
    TDEUniqueApplication()
  { }

  int newInstance(TQValueList<TQCString> params);
};

bool tryLookup(const char* node, const char *serv)
{
  int error;
  TQString _node = TQString::fromLatin1(node);
  TQString _serv = TQString::fromLatin1(serv);

  printf("\tTrying to lookup %s|%s... ", node, serv);
  TQPtrList<KAddressInfo> list = KExtendedSocket::lookup(_node, _serv, 0, &error);
  list.setAutoDelete(true);
  if (!list.isEmpty())
    {
      printf("worked\n");
      return true;
    }

  printf("failed\n\tReason was: %s\n",
	 (const char*)KExtendedSocket::strError(IO_LookupError, error).local8Bit());
  return false;
}

#ifdef AF_INET6
bool try_ntop()
{
  char buf[50];			// 46 is enough
  kde_in6_addr in;

  memset(&in, 0, sizeof(in));
  ((unsigned char*)&in)[15] = 1;	// set this to be ::1

  printf("\tTrying to convert ::1 into string...");
  if (inet_ntop(AF_INET6, &in, buf, sizeof(buf)) == NULL)
    {
      printf("failed\n");
      return false;
    }

  printf("suceeded\n\treturned '%s'\n", buf);
  return strcmp(buf, "::1") == 0;
}

bool try_pton()
{
  const char *buf = "::1";
  kde_in6_addr in;

  printf("\tTrying to convert '::1' into binary form...");
  if (inet_pton(AF_INET6, buf, &in) == 0)
    {
      printf("failed\n");
      return false;
    }

  if (KDE_IN6_IS_ADDR_LOOPBACK(&in))
    {
      printf("succeeded\n");
      return true;
    }

  printf("claims to have suceeded, but returned invalid value\n");
  return false;
}
#endif

bool tryLookup6(const char *node, const char *serv)
{
  int error;
  TQString _node = TQString::fromLatin1(node);
  TQString _serv = TQString::fromLatin1(serv);

  printf("\tTrying to lookup IPv6 of %s|%s... ", node, serv);
  TQPtrList<KAddressInfo> list = KExtendedSocket::lookup(_node, _serv, KExtendedSocket::ipv6Socket, &error);
  list.setAutoDelete(true);
  if (!list.isEmpty())
    {
      printf("worked\n");
      return true;
    }

  printf("failed\n\tReason was: %s\n",
	 (const char*)KExtendedSocket::strError(IO_LookupError, error).local8Bit());
  return false;
}

bool testKernel()
{
#ifndef AF_INET6
  printf("\tAF_INET6 unknown. Can't test anything\n");
  return false;

#else
  int sock;
  kde_sockaddr_in6 sin6;
  socklen_t len = sizeof(sin6);

  printf("\tAF_INET6 == %d\n", AF_INET6);
  printf("\tTrying to create an IPv6 socket...");
  sock = socket(AF_INET6, SOCK_STREAM, 0);
  if (sock == -1)
    printf("failed\n\tReason was: %s", strerror(errno));
  else
    {
      printf("succeeded\n");

      if (getsockname(sock, (struct sockaddr*)&sin6, &len) == 0)
	printf("\tSize of kernel's sockaddr_in6 is %lu bytes\n", (unsigned long)len);
      else
	printf("\tCould not get socket name\n");
    }

  printf("\tSize of TDE's internal sockaddr_in6 is %lu bytes\n",
	 (unsigned long)sizeof(kde_sockaddr_in6));

# ifdef HAVE_SOCKADDR_IN6
  printf("\tSize of system libraries' sockaddr_in6 is %lu bytes\n",
	 (unsigned long)sizeof(sockaddr_in6));
# else
  printf("\tSystem libraries don't define sockaddr_in6\n");
# endif

  if (sock == -1)
    return false;

  printf("\tTrying to bind the socket to an address...");
  sin6.sin6_family = AF_INET6;
# ifdef HAVE_SOCKADDR_SA_LEN
  sin6.sin6_len = sizeof(sin6);
# endif
  sin6.sin6_flowinfo = 0;
  sin6.sin6_scope_id = 0;
  sin6.sin6_port = 0;		// bind to any port
  memset(&sin6.sin6_addr, 0, sizeof(sin6.sin6_addr)); // any address

  if (bind(sock, (sockaddr*)&sin6, sizeof(sin6)) == -1)
    {
      printf("failed\n\tReason was: %s\n", strerror(errno));
      close(sock);
      return false;
    }

  printf("succeeded\n");

  TDESocketAddress *ksin = KExtendedSocket::localAddress(sock);
  if (ksin != NULL)
    {
      printf("\tWe got socket %s\n", (const char*)ksin->pretty().latin1());
      delete ksin;
    }

  close(sock);
  return true;
#endif // AF_INET6
}

bool tryConnectLocal()
{
  KExtendedSocket ks1("::", "0", KExtendedSocket::ipv6Socket | KExtendedSocket::passiveSocket),
    ks2;
  const KInetSocketAddress *ksin1, *ksin2;

  printf("Attempting a loop-back connection\n\tTrying to listen on socket...");
  if (ks1.listen() != 0)
    {
      printf("failed\n\tReason was: %s\n",
	     (const char*)KExtendedSocket::strError(ks1.status(), ks1.systemError()).local8Bit());
      return false;
    }

  ks1.setBlockingMode(false);
  ksin1 = (KInetSocketAddress*)ks1.localAddress();

  printf("succeeded\n\tWe have socket %s listening\n",
	 (const char*)ksin1->pretty().local8Bit());

  ks2.setAddress("::1", ksin1->port());
  ks2.setSocketFlags(KExtendedSocket::ipv6Socket | KExtendedSocket::noResolve);

  printf("\tTrying to connect to that socket...");
  if (ks2.connect() != 0)
    {
      printf("failed\n\tReason was: %s\n",
	     (const char*)KExtendedSocket::strError(ks2.status(), ks2.systemError()).local8Bit());
      return false;
    }

  printf("suceeded\n");

  ksin2 = dynamic_cast<const KInetSocketAddress *>(ks2.localAddress());
  printf("\tIf you may flip to another terminal/xterm and run netstat to see\n"
	 "\tthis connection. It should be a connection from %s to %s.\n"
	 "\tPress any key to continue\n", 
	 (const char*)ksin2->pretty().local8Bit(), (const char*)ksin1->pretty().local8Bit());
  getchar();
  return true;
}

bool tryConnectRemote()
{
  KExtendedSocket ks("www.6bone.net", "80", KExtendedSocket::ipv6Socket);

  printf("\nAttempting a remote connection to www.6bone.net|80\n");

  if (ks.connect() != 0)
    {
      printf("\tConnection failed with error: %s\n",
	     (const char*)KExtendedSocket::strError(ks.status(), ks.systemError()).local8Bit());
      return false;
    }

  printf("\tConnection succeeded\n");
  return true;
}

void go()
{
  int rf = KDE::resolverFlags;
  printf("The resolver claims to:\n");
  if (rf & KRF_USING_OWN_GETADDRINFO)
    {
      printf(" - Be using its own version of getaddrinfo()\n");
      if (rf & KRF_CAN_RESOLVE_UNIX)
	printf(" - Be able to resolve Unix-domain sockets\n");
      else
	printf(" - Be unable to resolve Unix-domain sockets -- This shouldn't happen\n");
      if (rf & KRF_CAN_RESOLVE_IPV4)
	printf(" - Be able to resolve IPv4 Internet sockets\n");
      else
	printf(" - Be unable to resolve IPv4 Internet sockets -- This shouldn't happen\n");
      if (rf & KRF_CAN_RESOLVE_IPV6)
	printf(" - Be able to resolve IPv6 Internet sockets\n");
      else
	printf(" - Be unable to resolve IPv6 Internet sockets\n");
    }
  else
    printf(" - Be using the system getaddrinfo()\n");

  if (rf & KRF_USING_OWN_INET_NTOP)
    printf(" - Be using its own inet_ntop()\n");
  else
    printf(" - Be using the system inet_ntop()\n");

  if (rf & KRF_USING_OWN_INET_PTON)
    printf(" - Be using its own inet_pton()\n");
  else
    printf(" - Be using the system inet_pton()\n");

  if (rf & KRF_KNOWS_AF_INET6)
    printf(" - To know the value of AF_INET6\n");
  else
    printf(" - Not to know the value of AF_INET6\n");

  printf("\n\nGeneral conclusion is:\n");
  if ((rf & KRF_USING_OWN_GETADDRINFO) == 0 &&
      rf & KRF_KNOWS_AF_INET6)
    printf(" Your system probably supports full IPv6 implementation.\n"
	   " This depends on whether your system's getaddrinfo() supports IPv6.\n"
	   " However, TDE Libraries were compiled to use the support whenever available.\n");
  else if ((rf & (KRF_USING_OWN_GETADDRINFO|KRF_KNOWS_AF_INET6)) == 0)
    printf(" Your system supports partial IPv6 implementation.\n"
	   " That is, your system has a getaddrinfo() implementation, but TDE Libraries\n"
	   " don't know how to detect an IPv6 socket. That means that only request to"
	   " any kind of socket will use IPv6, if your getaddrinfo() returns them.");
  else if (rf & KRF_USING_OWN_GETADDRINFO)
    {
      if (rf & KRF_KNOWS_AF_INET6)
	{
	  printf(" Your system supports partial IPv6 implementation.\n");
	  if (rf & KRF_CAN_RESOLVE_IPV6)
	    printf(" The TDE implementation of getaddrinfo() claims to be able to resolve\n"
		   " IPv6 lookups and the value of AF_INET6 is known.\n");
	  else
	    printf(" The TDE implementation of getaddrinfo() cannot resolve IPv6 lookups.\n"
		   " That means that IPv6 support is limited to two addresses (:: and ::1)\n");
	}
      else
	printf(" Your system doesn't support IPv6\n");
    }

  /* Make sure KSocks doesn't interfere in testing */
  KSocks::disable();

  /* Begin testing */
  printf("\nReady to start testing\nPress any key to continue...");
  getchar();
  printf("\n");

  /* Start with simple lookups */
  printf("Trying simple lookups\n"
	 "All of the following look ups should work\n\n");
  tryLookup(NULL, "/tmp/something");
  tryLookup("127.0.0.1", "80");
  tryLookup("localhost", "http");

#ifdef AF_INET6
  printf("\nPress any key for next test");
  getchar();

  printf("\nThis test determines if the inet_ntop and inet_pton functions work\n");
  try_ntop();
  try_pton();
#endif

  printf("\nPress any key for next test");
  getchar();

  printf("\nThis test determines how far the IPv6 resolution can go\n");
  if (!tryLookup6("::1", "80"))
    printf("Your system can't resolve a numeric IPv6 address\n");
  else if (!tryLookup6("localhost", "80"))
    printf("Your system can resolve a numeric IPv6 address, but not localhost\n");
  else if (!tryLookup6("www.6bone.net", "80"))
    printf("Your system can resolve numeric IPv6 addresses and localhost, \n"
	   "but cannot resolve an external address to IPv6 (www.6bone.net)\n");
  else
    printf("Your system can resolve any kind of IPv6 addresses\n");

  printf("\nPress any key for next test");
  getchar();

  printf("\nThis test determines how supported IPv6 is in your kernel\n");
  testKernel();

  printf("\nPress any key for next test");
  getchar();

  printf("\nThis test determines if you can connect to IPv6 addresses via TCP\n");
  tryConnectLocal();
  tryConnectRemote();

  printf("\n\nTest finished\n");
}

int TestApp::newInstance(TQValueList<TQCString> /*params*/)
{
  go();
}

int main(int argc, char **argv)
{
  TDEAboutData about("socktest2", "SockTest", "1.0");
  TDECmdLineArgs::init(argc, argv, &about);
  TDEUniqueApplication::addCmdLineOptions();

  /*  TestApp a;
      a.exec();*/
  go();
}
