/* a simplest zone type: a list of IP addresses with
 * a common text (maybe with a substitution for IP).
 * A list is stored as a sorted array, with binary search --
 * maybe not the most efficient way, but it's the most optimal
 * from the memory requiriments point of view, simple to
 * handle and sufficient enouth (imho)
 */

#include <syslog.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "rbldnsd.h"
#include "dns.h"

struct zonedata {
  ip4addr_t r_a;
  char *r_txt;
  unsigned n[4]; /* counts */
  unsigned a[4]; /* allocated - used only when loading */
  ip4addr_t *e[4]; /* entries */
};

/* indexes */
#define E32 0
#define E24 1
#define E16 2
#define E08 3

void ip4set_free(struct zonedata *z) {
  if (z) {
    if (z->r_txt) free(z->r_txt);
    if (z->e[E32]) free(z->e[E32]);
    if (z->e[E24]) free(z->e[E24]);
    if (z->e[E16]) free(z->e[E16]);
    if (z->e[E08]) free(z->e[E08]);
    free(z);
  }
}

static int
ip4set_parseline(struct zonedata *z, char *line, int lineno, int llines) {
  char *p;
  unsigned bits, idx, count;
  ip4addr_t a, step, *e;

  if (!llines && line[0] == ':') {
    /* default entry */
    if (!addrtxt(line, &a, &p)) {
      zlog(LOG_WARNING, lineno, "invalid default entry");
      return 1;
    }
    if (a) z->r_a = a;
    if (*p && (z->r_txt = estrdup(p)) == NULL)
      return 0;
    return 1;
  }
  
  /* normal entry */
  for(p = line; *p; ++p)
    if (*p == ' ' || *p == '\t' || *p == '#') break;
  *p = '\0'; /* skip any additional info */
  if (!(bits = ip4cidr(line, &a))) {
    zlog(LOG_WARNING, lineno, "invalid entry");
    return 1;
  }

  idx = 3 - (--bits>>3);
  count = 1 << ((31 - bits) & 7);
  step = 1 << (24 - (bits & 24));
  e = z->e[idx];
#if DEBUG
  printf("%s/%02d: idx=%d step=%08x count=%03d\n",
         ip4atos(a), bits+1, idx, step, count);
#endif

  if (z->n[idx] + count > z->a[idx]) {
    do z->a[idx] = z->a[idx] ? z->a[idx] << 1 : 32;
    while(z->n[idx] + count > z->a[idx]);
    e = z->e[idx] = (ip4addr_t*)erealloc(e, z->a[idx] * sizeof(*e));
    if (!e)
      return 0;
  }

  e += z->n[idx];
  z->n[idx] += count;
  for(; count--; a += step)
    *e++ = a;
  return 1;

}

static int ip4set_cmpent(const ip4addr_t *a, const ip4addr_t *b) {
  if (*a < *b) return -1;
  if (*a > *b) return 1;
  return 0;
}

struct zonedata *ip4set_load(FILE *f) {
  int r;
  struct zonedata *z = (struct zonedata *)emalloc(sizeof(*z));
  if (!z) return NULL;
  memset(z, 0, sizeof(*z));
  z->r_a = R_A_DEFAULT;
  if (!readzlines(f, z, ip4set_parseline)) {
    ip4set_free(z);
    return NULL;
  }
  for(r = 0; r < 4; ++r) {
    if (!z->n[r]) continue;
    if (z->a[r] != z->n[r])
      z->e[r] = (ip4addr_t*)realloc(z->e[r], z->n[r] * sizeof(ip4addr_t));
    qsort(z->e[r], z->n[r], sizeof(ip4addr_t),
          (int(*)(const void*, const void*))ip4set_cmpent);
  }
  zloaded("e32/24/16/8=%u/%u/%u/%u", 
          z->n[E32], z->n[E24], z->n[E16], z->n[E08]);
  return z;
}

static int ip4set_find(const ip4addr_t *e, int b, ip4addr_t q) {
  int a = 0, m;
  while(a <= b) {
    m = (a + b) >> 1;
    if (e[m] == q) return 1;
    if (e[m] < q) a = m + 1;
    else b = m - 1;
  }
  return 0;
}

int ip4set_query(const struct zonedata *const z, struct dnspacket *p,
            const unsigned char *const query, unsigned qflg)
{
  ip4addr_t q = dntoip4addr(query);
  if (!q) return 0;
#define try(i,mask) \
    (z->n[i] && ip4set_find(z->e[i], z->n[i] - 1, q & mask))
  if (!try(E32, 0xffffffff) && !try(E24, 0xffffff00) &&
      !try(E16, 0xffff0000) && !try(E08, 0xff000000))
    return 0;
  if (qflg & QUERY_A) addrec_a(p, z->r_a);
  if (qflg & QUERY_TXT && z->r_txt) addrec_txt(p, z->r_txt, ip4atos(q));
  return 1;
}
