/*****************************
File:       inetaddr.CPP
Language:   C++ (header)
Project:    H3DNetworkingUtils
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/

Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
License for the specific language governing rights and limitations
under the License.

The Original Code is H3DNetworkingUtils v1.0.

The Initial Developer of the Original Code is CSIRO.
Portions created by the Initial Developer are Copyright (C) 1009 CSIRO. All Rights Reserved.
Contributor(s):
    Chris Gunn  <Chris.Gunn@csiro.au> <ChrisJGunn@gmail.com>
***************************/

/* Distributed under a BSD type license. See the netwrap COPYRIGHT file */

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

#include "H3DNetworkingUtils/fail.h"
#include "H3DNetworkingUtils/addrconv.h"

#include "H3DNetworkingUtils/inetaddr.h"

#ifndef IN_CLASSD
#define IN_CLASSD(i)            (((long)(i) & 0xf0000000) == 0xe0000000)
#define IN_CLASSD_NET           0xf0000000       /* These ones aren't really */
#define IN_CLASSD_NSHIFT        28               /* net and host fields, but */
#define IN_CLASSD_HOST          0x0fffffff       /* routing needn't know.    */
#define IN_MULTICAST(i)         IN_CLASSD(i)
#endif

// Space for hostname
static char MyHostName[256]; // Max fully qualified name in DNS

//****	 Table of local addresses for testing	****

struct in_addr * localAddr = NULL;
int nLocalAddr = 0;

// Must detect first use under Windows. See also sockwrap.cpp
static int SockFirstUse = 0;

static void WinInitCheck ()
{
    WORD vers;
    WSADATA data;

    if (! SockFirstUse) {
	vers = MAKEWORD(1, 1);
	FailStatus(WSAStartup(vers, &data), "WSAStartup");
	SockFirstUse = 1;
    }
}

static void buildLocalAddrTable ()
{
  char myname[256];
  int  n;
  struct hostent * info;

  // Who are we?
  gethostname(myname, sizeof(myname));
  info = gethostbyname(myname);
  FailNull(info, "buildLocalAddrTable");

  // Allow for max 8 addresses - this is a quick hack
  localAddr  = (struct in_addr *)malloc(sizeof(struct in_addr) * 8);
  FailNull(localAddr, "buildLocalAddrTable");
  nLocalAddr = 0;

  // Loop through addresses
  n = 0;
  while (n < 8 && info->h_addr_list[n] != NULL) {
    localAddr[n] = *((struct in_addr *)(info->h_addr_list[n]));
    n ++;
  }
  nLocalAddr = n;
}


InetAddr::InetAddr ()
{
  WinInitCheck();

  address.sin_family = AF_INET;
  address.sin_port   = htons(0);
  address.sin_addr.s_addr = htonl(INADDR_ANY);
  addressStr[0] = 0;
}

InetAddr::~InetAddr ()
{
}

int InetAddr::family ()
{
  return address.sin_family;
}

int InetAddr::port ()
{
  return ntohs(address.sin_port);
}
 
void InetAddr::setPort (int newPort)
{
	address.sin_port = htons(newPort);
}

void * InetAddr::addr ()
{
  return &address;
}

int InetAddr::addrSize ()
{
  return sizeof(address);
}

InetAddr * InetAddr::clone ()
{
  InetAddr * result;
  
  result = new InetAddr();
  memcpy(result->addr(), &address, sizeof(address));
  
  return result;
}

void InetAddr::scan (char * addr)
{
  char buf[512];
  char * split;

  strcpy(buf, addr);
  // Host or host and port?
  split = strrchr(buf, '/');
  if (split == NULL)
    split = strrchr(buf, ':');

  // Port?
  if (split != NULL)
    this->scanPort(split + 1);

  // Host?
  if (split != NULL)
    *split = 0;
  this->scanHost(buf);
}

void InetAddr::scanHost (char * host)
{
  struct hostent * hostPtr;
  char		   msg[256];
  
  FailNull(host, "InetAddr::scanHost");
  
  if ((hostPtr = gethostbyname(host)) != NULL)
    memcpy(&address.sin_addr, hostPtr->h_addr, hostPtr->h_length);
  else if (inet_pton(AF_INET, host, &address.sin_addr.s_addr) < 0) {
    sprintf(msg, "Cannot resolve host \"%s\"", host);
    Fail(msg);
  }
}

void InetAddr::scanPort (char * port)
{
  struct servent * servicePtr;
  char		   msg[256];
  
  FailNull(port, "InetAddr::scanPort");
  
  if ((servicePtr = getservbyname(port, "tcp")) != NULL ||
      (servicePtr = getservbyname(port, "udp")) != NULL)
    address.sin_port = servicePtr->s_port;
  else if (atoi(port) > 0)
    address.sin_port = htons(atoi(port));
  else {
    sprintf(msg, "Cannot get port \"%s\"", port);
    Fail(msg);
  }
}

char * InetAddr::text ()
{
  char   port[8];
  
  // Not worth trying to detect repeat requests
  inet_ntop(address.sin_family, &address.sin_addr,
		addressStr, sizeof(addressStr));
  sprintf(port, ":%d", ntohs(address.sin_port));
  strcat(addressStr, port);

  return addressStr;
}

int InetAddr::isUnicast ()
{
  long ipnum;

  ipnum = ntohl(address.sin_addr.s_addr);
  return IN_CLASSA(ipnum) ||
	 IN_CLASSB(ipnum) ||
	 IN_CLASSC(ipnum);
}

int InetAddr::isMulticast ()
{
  long ipnum;

  ipnum = ntohl(address.sin_addr.s_addr);
  return IN_MULTICAST(ipnum);
}

// IP has no notion of a host address, only interfaces, so it is
// meaningless to ask for "the local address". What you can do
// is find out whether the address belongs to your host or not.

int InetAddr::isLocal ()
{
  int i;
  
  if (address.sin_addr.s_addr == INADDR_ANY ||
      address.sin_addr.s_addr == INADDR_LOOPBACK)
    return 1;
  else {
    if (localAddr == NULL)
      buildLocalAddrTable();
    for (i = 0; i < nLocalAddr; i++)
      if (address.sin_addr.s_addr == localAddr[i].s_addr)
        return 1;
    // No go
    return 0;
  }
}

int InetAddr::eqHost (InetAddr * other)
{
  if (other == NULL)
    return 0;
  else
    return address.sin_addr.s_addr =
	   ((struct sockaddr_in *)other->addr())->sin_addr.s_addr;
}

int InetAddr::eqPort (InetAddr * other)
{
  if (other == NULL)
    return 0;
  else
    return this->port() == other->port();
}

//****	Host properties


char * InetAddr::HostName ()
{
  char * stop;

  // Call each time, not worth trying to save previous
  gethostname(MyHostName, sizeof(MyHostName));
  // Unqualified?
  stop = strchr(MyHostName, '.');
  if (stop != NULL)
    *stop = 0;

  return MyHostName;
}
