#include "gsknameresolver.h"
#include "gskerror.h"
#include "gskmacros.h"

/* for dns support */
#include "dns/gskdnsclient.h"
#include "gskpacketqueuefd.h"

GType gsk_name_resolver_get_type(void)
{
  static GType name_resolver_type = 0;
  if (!name_resolver_type)
    {
      static const GTypeInfo name_resolver_info =
      {
	sizeof (GskNameResolverIface),
	(GBaseInitFunc) NULL,
	(GBaseFinalizeFunc) NULL,
	(GClassInitFunc) NULL,
	NULL,		/* class_finalize */
	NULL,		/* class_data */
	0,
	0,		/* n_preallocs */
	NULL,		/* instance_init */
	NULL		/* value_table */
      };
      name_resolver_type = g_type_register_static (G_TYPE_INTERFACE,
                                                   "GskNameResolver",
						   &name_resolver_info,
						   G_TYPE_FLAG_ABSTRACT);
    }
  return name_resolver_type;
}

/* --- resolver-tasks --- */
struct _GskNameResolverTask
{
  guint16                     ref_count;

  guint16		      is_running : 1;
  guint16                     was_cancelled : 1;
  guint16                     cancel_succeeded : 1;
  guint16                     task_succeeded : 1;

  gpointer                    task_data;

  GskNameResolver            *resolver;
  GskNameResolverIface       *iface;

  GskNameResolverSuccessFunc  success;
  GskNameResolverFailureFunc  failure;
  gpointer                    func_data;
  GDestroyNotify              destroy;
};

GSK_DECLARE_POOL_ALLOCATORS(GskNameResolverTask, gsk_name_resolver_task, 8)

typedef struct _Handler Handler;
struct _Handler
{
  GskNameResolverFamilyHandler handler;
  gpointer data;
  GDestroyNotify destroy;
  GskNameResolver *resolver;
};

static void
handler_destroy (gpointer data)
{
  Handler *handler = data;
  if (handler->destroy)
    handler->destroy (handler->data);
  if (handler->resolver)
    g_object_unref (handler->resolver);
  g_free (handler);
}

/* global tables */
static GHashTable *family_to_handler = NULL;
static GHashTable *family_to_name = NULL;
static GHashTable *name_to_family = NULL;
static guint       last_family = 0;

G_LOCK_DEFINE_STATIC (family_registry);
#define LOCK()		G_LOCK(family_registry)
#define UNLOCK()	G_UNLOCK(family_registry)


static void
handle_resolver_success (GskSocketAddress *address,
			 gpointer          task_ptr)
{
  GskNameResolverTask *task = task_ptr;
  if (task->success)
    (*task->success) (address, task->func_data);
  task->is_running = 0;
  task->task_succeeded = 1;
}

static void
handle_resolver_failure (GError           *error,
			 gpointer          task_ptr)
{
  GskNameResolverTask *task = task_ptr;
  if (task->failure)
    (*task->failure) (error, task->func_data);
  task->is_running = 0;
  task->task_succeeded = 0;
}

/**
 * gsk_name_resolver_task_new:
 * @family: name space to look the address up in.
 * @name: name within @family's namespace.
 * @success: function to be called with an appropriate #GskSocketAddress
 *    once the name is successfully resolved.
 * @failure: function to call if the name lookup failed.
 * @func_data: data to pass to @success or @failure.
 * @destroy: optionally called after @success or @failure, to deallocate
 * func_data usually.
 *
 * Begin a name lookup.  This may succeed before the function returns.
 * It you wish to cancel a name resolution task, call
 * gsk_name_resolver_task_cancel().  In any event,
 * you must gsk_name_resolver_task_unref() once you are done
 * with the handle.  (This will NOT cause a running task to be cancelled.)
 *
 * returns: a reference to a #GskNameResolverTask which can
 * be used to cancel or query the task.
 */
GskNameResolverTask *
gsk_name_resolver_task_new (GskNameResolverFamily       family,
		            const char                 *name,
		            GskNameResolverSuccessFunc  success,
		            GskNameResolverFailureFunc  failure,
		            gpointer                    func_data,
		            GDestroyNotify              destroy)
{
  GskNameResolverTask *task;
  Handler *handler;

  task = gsk_name_resolver_task_alloc ();
  task->ref_count = 2;
  task->success = success;
  task->failure = failure;
  task->func_data = func_data;
  task->destroy = destroy;
  task->task_data = NULL;
  task->is_running = 1;
  task->was_cancelled = 0;
  task->cancel_succeeded = 0;
  task->task_succeeded = 0;

  LOCK ();
  handler = g_hash_table_lookup (family_to_handler, GUINT_TO_POINTER (family));
  UNLOCK ();
  if (handler != NULL && handler->resolver == NULL)
    handler->resolver = (*handler->handler) (handler->data);
  if (handler)
    task->resolver = handler->resolver;
  else
    task->resolver = NULL;

  if (task->resolver == NULL)
    {
      const char *name = gsk_name_resolver_family_get_name (family);
      GError *error = g_error_new (GSK_G_ERROR_DOMAIN,
				   GSK_ERROR_BAD_ADDRESS,
				   _("no handler for address family %d (%s)"),
				   family,
				   name ? name : "*unknown*");
      handle_resolver_failure (error, task);
      gsk_name_resolver_task_unref (task);
    }
  else
    {
      task->iface = GSK_NAME_RESOLVER_GET_IFACE (task->resolver);
      task->task_data = (*task->iface->start_resolve) (task->resolver,
						       family, name,
						       handle_resolver_success,
						       handle_resolver_failure,
						       task,
						       (GDestroyNotify) gsk_name_resolver_task_unref);
    }
  return task;
}

/**
 * gsk_name_resolver_task_cancel:
 * @task: a running name resolution task to cancel.
 *
 * Stops the name lookup from continuing.
 * Neither the success or failure functions will be invoked subsequently,
 * but the destroy method will be.
 */
void
gsk_name_resolver_task_cancel (GskNameResolverTask *task)
{
  g_return_if_fail (task->is_running);
  g_return_if_fail (!task->was_cancelled);
  task->was_cancelled = 1;
  if (!task->iface->cancel_resolve (task->resolver, task->task_data))
    {
      task->cancel_succeeded = 0;
    }
  else
    {
      task->cancel_succeeded = 1;
      g_return_if_fail (!task->is_running);
    }
}

/**
 * gsk_name_resolver_task_ref:
 * @task: task whose reference count should be increased.
 *
 * Increase the reference count on a name-resolver task.
 * This is mostly useless outside the resolver code.
 */
void
gsk_name_resolver_task_ref (GskNameResolverTask *task)
{
  g_return_if_fail (task->ref_count > 0);
  ++(task->ref_count);
  g_return_if_fail (task->ref_count != 0);
}

/**
 * gsk_name_resolver_task_unref:
 * @task: task whose reference count should be decreased.
 *
 * Decrease the reference count on a name-resolver task, freeing it if needed.
 * This does NOT cancel the task.  You MUST unref the task returned by
 * gsk_name_resolve().
 */
void
gsk_name_resolver_task_unref (GskNameResolverTask *task)
{
  g_return_if_fail (task->ref_count > 0);
  if (--(task->ref_count) == 0)
    {
      g_return_if_fail (!task->is_running);
      if (task->destroy)
	(*task->destroy) (task->func_data);
      gsk_name_resolver_task_free (task);
    }
}

/* --- family handling --- */
/**
 * gsk_name_resolver_family_get_by_name:
 * @name: the name of the namespace, as a c string.
 *
 * Get the #GskNameResolverFamily of a resolver namespace
 * by ascii string.
 *
 * returns: the family, or 0 on error.
 */
GskNameResolverFamily
gsk_name_resolver_family_get_by_name (const char *name)
{
  GskNameResolverFamily family;
  LOCK ();
  family = GPOINTER_TO_UINT (g_hash_table_lookup (name_to_family, name));
  UNLOCK ();
  return family;
}


/**
 * gsk_name_resolver_family_get_name:
 * @family: the namespace family to enquire about.
 *
 * Get the resolver-namespace as a printable c string.
 *
 * returns: the namespace's name as a c string.
 */
const char *
gsk_name_resolver_family_get_name(GskNameResolverFamily family)
{
  const char *result;
  LOCK ();
  result = g_hash_table_lookup (family_to_name, GUINT_TO_POINTER (family));
  UNLOCK ();
  return result;
}

/**
 * gsk_name_resolver_family_unique:
 * @name: name of a new namespace to register.
 *
 * Allocate a unique GskNameResolverFamily
 * given a new name, or return the old GskNameResolverFamily
 * if one already exists.
 *
 * returns: the family corresponding to @name.
 */
GskNameResolverFamily
gsk_name_resolver_family_unique (const char *name)
{
  GskNameResolverFamily family = gsk_name_resolver_family_get_by_name (name);
  if (!family)
    {
      LOCK ();
      family = ++last_family;
      UNLOCK ();
      gsk_name_resolver_add_family_name (family, name);
    }
  return family;
}

/**
 * gsk_name_resolver_add_family_name:
 * @family: registered family to give a new name for.
 * @name: alias name for @family.
 *
 * Add a new nickname for the name resolver family.
 *
 * The family is the name of the namespace.
 */
void
gsk_name_resolver_add_family_name    (GskNameResolverFamily   family,
				      const char             *name)
{
  char *copy;
  LOCK ();
  g_return_if_fail (g_hash_table_lookup (name_to_family, name) == NULL);
  copy = g_strdup (name);
  if (g_hash_table_lookup (family_to_name, GUINT_TO_POINTER (family)) == NULL)
    g_hash_table_insert (family_to_name, GUINT_TO_POINTER (family), copy);
  g_hash_table_insert (name_to_family, copy, GUINT_TO_POINTER (family));
  if (family > last_family)
    last_family = family;
  UNLOCK ();
}

/**
 * gsk_name_resolver_add_family_resolver:
 * @family: registered family to provide an alias for.
 * @resolver: name resolver to use for addresses in @family.
 *
 * Add a name-resolver that will handle a request
 * of a given family.
 */
void
gsk_name_resolver_add_family_resolver (GskNameResolverFamily   family,
				       GskNameResolver        *resolver)
{
  Handler *handler;
  g_return_if_fail (GSK_IS_NAME_RESOLVER (resolver));
  handler = g_new (Handler, 1);
  handler->resolver = g_object_ref (resolver);
  handler->destroy = NULL;
  LOCK ();
  g_hash_table_insert (family_to_handler, GUINT_TO_POINTER (family), handler);
  UNLOCK ();
}

/**
 * gsk_name_resolver_add_family_handler:
 * @family: registered family to provide a resolver implementation for.
 * @handler: ...
 * @data: data to pass to @handler
 * @destroy: function to call when the handler has deregistered.
 *
 * Add a name-resolver that will handle a request
 * of a given family.
 */
void
gsk_name_resolver_add_family_handler (GskNameResolverFamily family,
				      GskNameResolverFamilyHandler handler,
				      gpointer data,
				      GDestroyNotify destroy)
{
  Handler *h;
  h = g_new (Handler, 1);
  h->resolver = NULL;
  h->handler = handler;
  h->data = data;
  h->destroy = destroy;
  LOCK ();
  g_hash_table_insert (family_to_handler, GUINT_TO_POINTER (family), h);
  UNLOCK ();
}

/**
 * gsk_name_resolver_lookup:
 * @family: name family to perform the lookup in.
 * @name: name to lookup.
 * @success: callback for successful name-lookup: this will
 * be called with the #GskSocketAddress that was found.
 * @failure: callback for failure.  This is invoked with the
 * #GError object.
 * @func_data: data to call to the callbacks.
 * @destroy: function to be called after the callbacks are done.
 *
 * Begin a non-cancellable name-lookup.
 */

/* --- global initialization --- */
static gboolean made_dns_name_resolver = FALSE;
static GskDnsRRCache *dns_rr_cache = NULL;

static GskNameResolver *
make_dns_client (gpointer unused)
{
  GskPacketQueue *queue;
  GskSocketAddressClass *class;
  GskDnsClient *client;
  GError *error = NULL;

  g_assert (unused == NULL);

  class = g_type_class_ref (GSK_TYPE_SOCKET_ADDRESS_IPV4);
  queue = gsk_packet_queue_fd_new_by_family (class->protocol_family, &error);
  if (queue == NULL)
    {
      g_warning ("error creating dns client file-descriptor: %s",
		 error->message);
      g_free (error);
      return NULL;
    }
  g_type_class_unref (class);
  client = gsk_dns_client_new (queue, dns_rr_cache, 0);
  g_object_unref (queue);
  if (!gsk_dns_client_parse_system_files (client))
    g_warning ("error initializing dns client");
  made_dns_name_resolver = TRUE;

  return GSK_NAME_RESOLVER (client);
}

void
_gsk_name_resolver_init (void)
{
  /* initialize the various hash-tables */
  family_to_name = g_hash_table_new (NULL, NULL);
  family_to_handler = g_hash_table_new_full (NULL, NULL, NULL, handler_destroy);
  name_to_family = g_hash_table_new (g_str_hash, g_str_equal);

  /* add dns handling by default */
  {
    GskNameResolverFamily family = gsk_name_resolver_family_unique ("ipv4");
    g_assert (family == GSK_NAME_RESOLVER_FAMILY_IPV4);
    gsk_name_resolver_add_family_handler (GSK_NAME_RESOLVER_FAMILY_IPV4,
					  make_dns_client, NULL, NULL);
  }
}

/**
 * gsk_name_resolver_set_dns_cache_size:
 * @max_bytes: maximum number of bytes of cached DNS data to keep around.
 * @max_records: maximum number of cached DNS records to keep around.
 *
 * Set the DNS cache size.
 *
 * Currently, the defaults are 128*1024 and 1024.
 *
 * This must be called before any name-resolving activities,
 * and may only be called once.
 */
void
gsk_name_resolver_set_dns_cache_size (guint64 max_bytes,
                                      guint   max_records)
{
  g_return_if_fail (!made_dns_name_resolver);
  g_return_if_fail (dns_rr_cache == NULL);
  dns_rr_cache = gsk_dns_rr_cache_new (max_bytes, max_records);
}


/**
 * gsk_name_resolver_set_dns_roundrobin:
 * @do_roundrobin: whether to support round-robin DNS.
 *
 * Set the DNS support for round-robin.
 *
 * You must call gsk_name_resolver_set_dns_cache_size()
 * before calling this function.
 *
 * Default is TRUE.
 */
void
gsk_name_resolver_set_dns_roundrobin (gboolean do_roundrobin)
{
  g_return_if_fail (dns_rr_cache != NULL);
  gsk_dns_rr_cache_roundrobin (dns_rr_cache, do_roundrobin);
}


syntax highlighted by Code2HTML, v. 0.9.1