/**
 * We're done modifying all records in the response.  Submit the reply
 * and free the resources of the rc.
 *
 * @param rc context to process
 */
static void
finish_request (struct ReplyContext *rc)
{
  char *buf;
  size_t buf_len;

  if (GNUNET_SYSERR ==
      GNUNET_DNSPARSER_pack (rc->dns,
			     MAX_DNS_SIZE,
			     &buf,
			     &buf_len))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
		_("Failed to pack DNS request.  Dropping.\n"));
    GNUNET_DNS_request_drop (rc->rh);
  }
  else
  {
    GNUNET_STATISTICS_update (stats,
			      gettext_noop ("# DNS requests mapped to VPN"),
			      1, GNUNET_NO);
    GNUNET_DNS_request_answer (rc->rh,
			       buf_len, buf);
    GNUNET_free (buf);
  }
  GNUNET_DNSPARSER_free_packet (rc->dns);
  GNUNET_free (rc);
}
/**
 * Signature of a function that is called whenever the DNS service
 * encounters a DNS request and needs to do something with it.  The
 * function has then the chance to generate or modify the response by
 * calling one of the three "GNUNET_DNS_request_*" continuations.
 *
 * When a request is intercepted, this function is called first to
 * give the client a chance to do the complete address resolution;
 * "rdata" will be NULL for this first call for a DNS request, unless
 * some other client has already filled in a response.
 *
 * If multiple clients exist, all of them are called before the global
 * DNS.  The global DNS is only called if all of the clients'
 * functions call GNUNET_DNS_request_forward.  Functions that call
 * GNUNET_DNS_request_forward will be called again before a final
 * response is returned to the application.  If any of the clients'
 * functions call GNUNET_DNS_request_drop, the response is dropped.
 *
 * @param cls closure
 * @param rh request handle to user for reply
 * @param request_length number of bytes in request
 * @param request udp payload of the DNS request
 */
static void 
modify_request (void *cls,
		struct GNUNET_DNS_RequestHandle *rh,
		size_t request_length,
		const char *request)
{
  struct GNUNET_DNSPARSER_Packet *p;
  unsigned int i;
  char *buf;
  size_t len;
  int ret;

  p = GNUNET_DNSPARSER_parse (request, request_length);
  if (NULL == p)
  {
    fprintf (stderr, "Received malformed DNS packet, leaving it untouched\n");
    GNUNET_DNS_request_forward (rh);
    return;
  }
  for (i=0;i<p->num_answers;i++)
    modify_record (&p->answers[i]);
  buf = NULL;
  ret = GNUNET_DNSPARSER_pack (p, 1024, &buf, &len);
  GNUNET_DNSPARSER_free_packet (p);
  if (GNUNET_OK != ret)
  {
    if (GNUNET_NO == ret)
      fprintf (stderr, 
	       "Modified DNS response did not fit, keeping old response\n");
    else
      GNUNET_break (0); /* our modifications should have been sane! */
    GNUNET_DNS_request_forward (rh);
  }
  else
  {
    if (verbosity > 0)
      fprintf (stdout,
	       "Injecting modified DNS response\n");
    GNUNET_DNS_request_answer (rh, len, buf);
  }
  GNUNET_free_non_null (buf);      
}
/**
 * Reply to dns request with the result from our lookup.
 *
 * @param cls the closure to the request (an InterceptLookupHandle)
 * @param rd_count the number of records to return
 * @param rd the record data
 */
static void
reply_to_dns (void *cls, uint32_t rd_count,
	      const struct GNUNET_GNSRECORD_Data *rd)
{
  struct InterceptLookupHandle *ilh = cls;
  struct GNUNET_DNSPARSER_Packet *packet = ilh->packet;
  struct GNUNET_DNSPARSER_Query *query = &packet->queries[0];
  uint32_t i;
  size_t len;
  int ret;
  char *buf;
  unsigned int num_answers;
  unsigned int skip_answers;
  unsigned int skip_additional;
  size_t off;

  /* Put records in the DNS packet */
  num_answers = 0;
  for (i=0; i < rd_count; i++)
    if (rd[i].record_type == query->type)
      num_answers++;
  skip_answers = 0;
  skip_additional = 0;

  {
    struct GNUNET_DNSPARSER_Record answer_records[num_answers];
    struct GNUNET_DNSPARSER_Record additional_records[rd_count - num_answers];

    packet->answers = answer_records;
    packet->additional_records = additional_records;
    /* FIXME: need to handle #GNUNET_GNSRECORD_RF_SHADOW_RECORD option
       (by ignoring records where this flag is set if there is any
       other record of that type in the result set) */
    for (i=0; i < rd_count; i++)
    {
      if (rd[i].record_type == query->type)
      {
	answer_records[i - skip_answers].name = query->name;
	answer_records[i - skip_answers].type = rd[i].record_type;
	switch(rd[i].record_type)
	{
	case GNUNET_DNSPARSER_TYPE_NS:
	case GNUNET_DNSPARSER_TYPE_CNAME:
	case GNUNET_DNSPARSER_TYPE_PTR:
	  answer_records[i - skip_answers].data.hostname
	    = GNUNET_DNSPARSER_parse_name (rd[i].data,
					   rd[i].data_size,
					   &off);
	  if ( (off != rd[i].data_size) ||
	       (NULL == answer_records[i].data.hostname) )
	  {
	    GNUNET_break_op (0);
	    skip_answers++;
	  }
	  break;
	case GNUNET_DNSPARSER_TYPE_SOA:
	  answer_records[i - skip_answers].data.soa
	    = GNUNET_DNSPARSER_parse_soa (rd[i].data,
					  rd[i].data_size,
					  &off);
	  if ( (off != rd[i].data_size) ||
	       (NULL == answer_records[i].data.soa) )
	  {
	    GNUNET_break_op (0);
	    skip_answers++;
	  }
	  break;
	case GNUNET_DNSPARSER_TYPE_SRV:
	  /* FIXME: SRV is not yet supported */
	  skip_answers++;
	  break;
	case GNUNET_DNSPARSER_TYPE_MX:
	  answer_records[i - skip_answers].data.mx
	    = GNUNET_DNSPARSER_parse_mx (rd[i].data,
					 rd[i].data_size,
					 &off);
	  if ( (off != rd[i].data_size) ||
	       (NULL == answer_records[i].data.hostname) )
	  {
	    GNUNET_break_op (0);
	    skip_answers++;
	  }
	  break;
	default:
	  answer_records[i - skip_answers].data.raw.data_len = rd[i].data_size;
	  answer_records[i - skip_answers].data.raw.data = (char*)rd[i].data;
	  break;
	}
	GNUNET_break (0 == (rd[i - skip_answers].flags & GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION));
	answer_records[i - skip_answers].expiration_time.abs_value_us = rd[i].expiration_time;
	answer_records[i - skip_answers].dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET;
      }
      else
      {
	additional_records[i - skip_additional].name = query->name;
	additional_records[i - skip_additional].type = rd[i].record_type;
	switch(rd[i].record_type)
	{
	case GNUNET_DNSPARSER_TYPE_NS:
	case GNUNET_DNSPARSER_TYPE_CNAME:
	case GNUNET_DNSPARSER_TYPE_PTR:
	  additional_records[i - skip_additional].data.hostname
	    = GNUNET_DNSPARSER_parse_name (rd[i].data,
					   rd[i].data_size,
					   &off);
	  if ( (off != rd[i].data_size) ||
	       (NULL == additional_records[i].data.hostname) )
	  {
	    GNUNET_break_op (0);
	    skip_additional++;
	  }
	  break;
	case GNUNET_DNSPARSER_TYPE_SOA:
	  additional_records[i - skip_additional].data.soa
	    = GNUNET_DNSPARSER_parse_soa (rd[i].data,
					  rd[i].data_size,
					  &off);
	  if ( (off != rd[i].data_size) ||
	       (NULL == additional_records[i].data.hostname) )
	  {
	    GNUNET_break_op (0);
	    skip_additional++;
	  }
	  break;
	case GNUNET_DNSPARSER_TYPE_MX:
	  additional_records[i - skip_additional].data.mx
	    = GNUNET_DNSPARSER_parse_mx (rd[i].data,
					 rd[i].data_size,
					 &off);
	  if ( (off != rd[i].data_size) ||
	       (NULL == additional_records[i].data.hostname) )
	  {
	    GNUNET_break_op (0);
	    skip_additional++;
	  }
	  break;
	case GNUNET_DNSPARSER_TYPE_SRV:
	  /* FIXME: SRV is not yet supported */
	  skip_answers++;
	  break;
	default:
	  additional_records[i - skip_additional].data.raw.data_len = rd[i].data_size;
	  additional_records[i - skip_additional].data.raw.data = (char*)rd[i].data;
	  break;
	}
	GNUNET_break (0 == (rd[i - skip_additional].flags & GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION));
	additional_records[i - skip_additional].expiration_time.abs_value_us = rd[i].expiration_time;
	additional_records[i - skip_additional].dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET;
      }
    }
    packet->num_answers = num_answers - skip_answers;
    packet->num_additional_records = rd_count - num_answers - skip_additional;
    packet->flags.authoritative_answer = 1;
    if (NULL == rd)
      packet->flags.return_code = GNUNET_TUN_DNS_RETURN_CODE_NAME_ERROR;
    else
      packet->flags.return_code = GNUNET_TUN_DNS_RETURN_CODE_NO_ERROR;
    packet->flags.query_or_response = 1;
    ret = GNUNET_DNSPARSER_pack (packet,
				 1024, /* maximum allowed size for DNS reply */
				 &buf,
				 &len);
    if (GNUNET_OK != ret)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
		  _("Error converting GNS response to DNS response!\n"));
    }
    else
    {
      GNUNET_DNS_request_answer (ilh->request_handle,
				 len,
				 buf);
      GNUNET_free (buf);
    }
    packet->num_answers = 0;
    packet->answers = NULL;
    packet->num_additional_records = 0;
    packet->additional_records = NULL;
    GNUNET_DNSPARSER_free_packet (packet);
  }
  GNUNET_CONTAINER_DLL_remove (ilh_head, ilh_tail, ilh);
  GNUNET_free (ilh);
}