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

struct entry {
  ip4addr_t addr; /* key */
  ip4addr_t r_a;  /* result: addr */
  char *r_txt;    /* result: txt (sstr) */
};

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

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

static void ip4vset_freeent(struct entry *e, int n) {
  while(n--)
    if (e[n].r_txt) sstrfree(e[n].r_txt);
  free(e);
}

void ip4vset_free(struct zonedata *z) {
  if (z) {
    if (z->e[E32]) ip4vset_freeent(z->e[E32], z->n[E32]);
    if (z->e[E24]) ip4vset_freeent(z->e[E24], z->n[E24]);
    if (z->e[E16]) ip4vset_freeent(z->e[E16], z->n[E16]);
    if (z->e[E08]) ip4vset_freeent(z->e[E08], z->n[E08]);
    if (z->r_txt) sstrfree(z->r_txt);
    free(z);
  }
}

static int
ip4vset_addent(struct zonedata *z, int idx, ip4addr_t addr,
              ip4addr_t r_a, char *r_txt) {
  struct entry *e = z->e[idx];
  if (z->n[idx] >= z->a[idx]) {
    z->a[idx] = z->a[idx] ? z->a[idx] << 1 : 32;
    e = z->e[idx] = (struct entry*)erealloc(e, z->a[idx] * sizeof(*e));
    if (!e) return 0;
  }
  e += z->n[idx]++;
  e->addr = addr;
  e->r_a = r_a ? r_a : z->r_a;
  if (!*r_txt) e->r_txt = NULL;
  else if ((e->r_txt = sstrdup(r_txt)) == NULL) return 0;
  return 1;
}

static int ip4vset_cmpent(const struct entry *a, const struct entry *b) {
  if (a->addr < b->addr) return -1;
  if (a->addr > b->addr) return 1;
  return 0;
}

static int
ip4vset_loadfile(FILE *f, struct zonedata *z) {
  char buf[512];
  int lineno = 0;
  int l;
  ip4addr_t a, step, r_a;
  unsigned bits;
  char *p;

  while(fgets(buf, sizeof(buf), f)) {
    ++lineno;
    l = strlen(buf);
    if (l && buf[l-1] == '\n') buf[--l] = '\0';
    if (!l || buf[0] == '#') continue;
    if (lineno == 1 && buf[0] == ':') {
      if (!addrtxt(buf, &a, &p)) {
        zlog(LOG_WARNING, 1, "invalid default entry");
        continue;
      }
      if (a) z->r_a = a;
      if (*p && (z->r_txt = esstrdup(p)) == NULL)
        return 0;
      continue;
    }
    for(p = buf; *p; ++p)
      if (*p == ' ' || *p == '\t') break;
    if (*p) *p++ = '\0';
    if (!(bits = ip4range(buf, &a))) {
      zlog(LOG_WARNING, lineno, "invalid address `%s'", buf);
      continue;
    }
    while(*p == ' ' || *p == '\t') ++p;
    if (*p && !addrtxt(p, &r_a, &p)) {
      zlog(LOG_WARNING, lineno, "invalid value");
      continue;
    }
    if      (bits > 24) step = 1 <<  0, bits = 1 << (32 - bits), l = E32;
    else if (bits > 16) step = 1 <<  8, bits = 1 << (24 - bits), l = E24;
    else if (bits >  8) step = 1 << 16, bits = 1 << (16 - bits), l = E16;
    else                step = 1 << 24, bits = 1 << ( 8 - bits), l = E08;
    for(; bits--; a += step)
      if (!ip4vset_addent(z, l, a, r_a, p)) return 0;
  }
  return 1;
}

struct zonedata *ip4vset_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 (!ip4vset_loadfile(f, z)) {
    ip4vset_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] = (struct entry*)realloc(z->e[r], z->n[r] * sizeof(struct entry));
    qsort(z->e[r], z->n[r], sizeof(struct entry),
          (int(*)(const void*, const void*))ip4vset_cmpent);
  }
  return z;
}

static const struct entry *
ip4vset_find(const struct entry *e, int b, ip4addr_t q) {
  int a = 0, m;
  while(a <= b) {
    m = (a + b) >> 1;
    if (e[m].addr == q) {
      while(m && e[m-1].addr == q) --m;
      return e + m;
    }
    if (e[m].addr < q) a = m + 1;
    else b = m - 1;
  }
  return NULL;
}

int ip4vset_query(const struct zonedata *const z, struct dnspacket *p,
            const unsigned char *const query, unsigned qflg)
{
  ip4addr_t q = dntoip4addr(query);
  const struct entry *e, *i, *t;
  if (!q) return 0;
#define try(i,mask) \
 (z->n[i] && (t = z->e[i] + z->n[i], e = ip4vset_find(z->e[i], z->n[i] - 1, q & mask)) != NULL)
  if (!try(E32, 0xffffffff) && !try(E24, 0xffffff00) &&
      !try(E16, 0xffff0000) && !try(E08, 0xff000000))
    return 0;
  if (qflg & QUERY_A) {
    i = e;
    do addrec_a(p, i->r_a);
    while(++i < t && i->addr == q);
  }
  if (qflg & QUERY_TXT) {
    const char *txt;
    const char *subst = ip4atos(q);
    i = e;
    do
      if ((txt = e->r_txt ? e->r_txt : z->r_txt) != NULL)
        addrec_txt(p, txt, subst);
    while(++i < t && i->addr == q);
  }
  return 1;
}
