/*
** JNetLib
** Copyright (C) 2000-2001 Nullsoft, Inc.
** Author: Justin Frankel
** File: asyncdns.cpp - JNL portable asynchronous DNS implementation
** License: see jnetlib.h
*/

#include "netinc.h"
#include "util.h"
#include "asyncdns.h"

JNL_AsyncDNS::JNL_AsyncDNS(int max_cache_entries)
{
  m_thread_kill=1;
  m_thread=0;
  m_cache_size=max_cache_entries;
  m_cache=(cache_entry *)::malloc(sizeof(cache_entry)*m_cache_size);
  memset(m_cache,0,sizeof(cache_entry)*m_cache_size);
}

JNL_AsyncDNS::~JNL_AsyncDNS()
{
#ifndef NO_DNS_SUPPORT
  m_thread_kill=1;

#ifdef _WIN32
  if (m_thread)
  {
    WaitForSingleObject(m_thread,INFINITE);
    CloseHandle(m_thread);
  }
#else
  if (m_thread)
  {
    void *p;
    pthread_join(m_thread,&p);
  }
#endif//!_WIN32
#endif//NO_DNS_SUPPORT
  free(m_cache);
}

#ifdef _WIN32
unsigned long WINAPI JNL_AsyncDNS::_threadfunc(LPVOID _d)
#else
unsigned int JNL_AsyncDNS::_threadfunc(void *_d)
#endif
{
#ifndef NO_DNS_SUPPORT
  int nowinsock=JNL::open_socketlib();
  JNL_AsyncDNS *_this=(JNL_AsyncDNS*)_d;
  int x;
  for (x = 0; x < _this->m_cache_size && !_this->m_thread_kill; x ++)
  {
    if (_this->m_cache[x].last_used && !_this->m_cache[x].resolved)
    {
      if (!nowinsock) 
      {
        if (_this->m_cache[x].mode==0)
        {
          struct hostent *hostentry;
          hostentry=::gethostbyname(_this->m_cache[x].hostname);
          if (hostentry)
          {
            _this->m_cache[x].addr=*((int*)hostentry->h_addr);
          }
          else
            _this->m_cache[x].addr=INADDR_NONE;
        }
        else if (_this->m_cache[x].mode==1)
        {
          struct hostent *ent;
          ent=::gethostbyaddr((const char *)&_this->m_cache[x].addr,4,AF_INET);
          if (ent)
          {
            strncpy(_this->m_cache[x].hostname,ent->h_name,255);
            _this->m_cache[x].hostname[255]=0;
          }
          else
          {
            _this->m_cache[x].hostname[0]=0;
          }
        }
        _this->m_cache[x].resolved=1;
      }
      else
      {
        if (_this->m_cache[x].mode==0)
        {
          _this->m_cache[x].addr=INADDR_NONE;
          _this->m_cache[x].resolved=1;
        }
        else if (_this->m_cache[x].mode==1)
        {
          _this->m_cache[x].hostname[0]=0;
          _this->m_cache[x].resolved=1;
        }
      }
    }
  }
  if (!nowinsock) JNL::close_socketlib();
  _this->m_thread_kill=1;
#endif // NO_DNS_SUPPORT

  return 0;
}

int JNL_AsyncDNS::resolve(char *hostname, unsigned long *addr)
{
  // return 0 on success, 1 on wait, -1 on unresolvable
  int x;
  unsigned long ip=inet_addr(hostname);
  if (ip != INADDR_NONE) 
  {
    *addr=ip;
    return 0;
  }
#ifndef NO_DNS_SUPPORT

  for (x = 0; x < m_cache_size; x ++)
  {
    if (!strcasecmp(m_cache[x].hostname,hostname) && m_cache[x].mode==0)
    {
      m_cache[x].last_used=time(NULL);
      if (m_cache[x].resolved)
      {
        if (m_cache[x].addr == INADDR_NONE)
        {
          return -1;
        }
        struct in_addr in;
        in.s_addr=m_cache[x].addr;
        *addr=m_cache[x].addr;
        return 0;
      }
      makesurethreadisrunning();
      return 1;
    }
  }
  // add to resolve list
  int oi=-1;
  for (x = 0; x < m_cache_size; x ++)
  {
    if (!m_cache[x].last_used)
    {
      oi=x;
      break;
    }
    if ((oi==-1 || m_cache[x].last_used < m_cache[oi].last_used) && m_cache[x].resolved)
    {
      oi=x;
    }
  }
  if (oi == -1)
  {
    return -1;
  }
  strcpy(m_cache[oi].hostname,hostname);
  m_cache[oi].mode=0;
  m_cache[oi].addr=INADDR_NONE;
  m_cache[oi].resolved=0;
  m_cache[oi].last_used=time(NULL);

  makesurethreadisrunning();
  return 1;
#else
  return -1;
#endif
}

int JNL_AsyncDNS::reverse(unsigned long addr, char *hostname)
{
  // return 0 on success, 1 on wait, -1 on unresolvable
  int x;
  if (addr == INADDR_NONE) 
  {
    return -1;
  }
#ifndef NO_DNS_SUPPORT
  for (x = 0; x < m_cache_size; x ++)
  {
    if (m_cache[x].addr==addr && m_cache[x].mode==1)
    {
      m_cache[x].last_used=time(NULL);
      if (m_cache[x].resolved)
      {
        if (!m_cache[x].hostname[0])
        {
          return -1;
        }
        strncpy(hostname,m_cache[x].hostname,255);
        hostname[255]=0;
        return 0;
      }
      makesurethreadisrunning();
      return 1;
    }
  }
  // add to resolve list
  int oi=-1;
  for (x = 0; x < m_cache_size; x ++)
  {
    if (!m_cache[x].last_used)
    {
      oi=x;
      break;
    }
    if ((oi==-1 || m_cache[x].last_used < m_cache[oi].last_used) && m_cache[x].resolved)
    {
      oi=x;
    }
  }
  if (oi == -1)
  {
    return -1;
  }
  m_cache[oi].addr=addr;
  m_cache[oi].hostname[0]=0;
  m_cache[oi].resolved=0;
  m_cache[oi].mode=1;
  m_cache[oi].last_used=time(NULL);

  makesurethreadisrunning();
  return 1;
#else
  return -1;
#endif
}


void JNL_AsyncDNS::makesurethreadisrunning(void)
{
#ifndef NO_DNS_SUPPORT
  if (m_thread_kill)
  {
  #ifdef _WIN32
    if (m_thread)
    {
      WaitForSingleObject(m_thread,INFINITE);
      CloseHandle(m_thread);
    }
    DWORD id;
    m_thread_kill=0;
    m_thread=CreateThread(NULL,0,_threadfunc,(LPVOID)this,0,&id);
    if (!m_thread)
    {
  #else
    if (m_thread)
    {
      void *p;
      pthread_join(m_thread,&p);
    }
    m_thread_kill=0;
    if (pthread_create(&m_thread,NULL,(void *(*) (void *))_threadfunc,(void*)this) != 0)
    {
  #endif
      m_thread_kill=1;
    }
  }
#endif//NO_DNS_SUPPORT
}
