/* 
Serval DNA configuration
Copyright (C) 2012 Serval Project Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <stdarg.h>
#include <assert.h>
#include <arpa/inet.h>

#include "log.h"
#include "debug.h"
#include "mem.h"
#include "str.h"
#include "uri.h"
#include "numeric_str.h"
#include "strbuf_helpers.h"
#include "conf.h"
#include "dataformats.h"

int cf_opt_boolean(bool_t *booleanp, const char *text)
{
  if (!strcasecmp(text, "true") || !strcasecmp(text, "yes") || !strcasecmp(text, "on") || !strcasecmp(text, "1")) {
    *booleanp = 1;
    return CFOK;
  }
  else if (!strcasecmp(text, "false") || !strcasecmp(text, "no") || !strcasecmp(text, "off") || !strcasecmp(text, "0")) {
    *booleanp = 0;
    return CFOK;
  }
  return CFINVALID;
}

int cf_fmt_boolean(const char **textp, const bool_t *booleanp)
{
  if (*booleanp == 1) {
    *textp = str_edup("true");
    return CFOK;
  }
  else if (*booleanp == 0) {
    *textp = str_edup("false");
    return CFOK;
  }
  return CFINVALID;
}

int cf_cmp_boolean(const bool_t *a, const bool_t *b)
{
  return !*a && *b ? -1 : *a && !*b ? 1 : 0;
}

int cf_opt_absolute_path(char *str, size_t len, const char *text)
{
  if (text[0] != '/')
    return CFINVALID;
  if (strlen(text) >= len)
    return CFSTRINGOVERFLOW;
  strncpy(str, text, len);
  assert(str[len - 1] == '\0');
  return CFOK;
}

int cf_fmt_absolute_path(const char **textp, const char *str)
{
  if (str[0] != '/')
    return CFINVALID;
  *textp = str_edup(str);
  return CFOK;
}

int cf_cmp_absolute_path(const char *a, const char *b)
{
  return strcmp(a, b);
}

int cf_opt_protocol(char *str, size_t len, const char *text)
{
  if (!str_is_uri_scheme(text))
    return CFINVALID;
  if (strlen(text) >= len)
    return CFSTRINGOVERFLOW;
  strncpy(str, text, len);
  assert(str[len - 1] == '\0');
  return CFOK;
}

int cf_fmt_protocol(const char **textp, const char *str)
{
  if (!str_is_uri_scheme(str))
    return CFINVALID;
  *textp = str_edup(str);
  return CFOK;
}

int cf_cmp_protocol(const char *a, const char *b)
{
  return strcmp(a, b);
}

int cf_opt_rhizome_peer(struct config_rhizome_peer *rpeer, const struct cf_om_node *node)
{
  if (!node->text)
    return cf_opt_config_rhizome_peer(rpeer, node);
  if (node->nodc) {
    cf_warn_incompatible_children(node);
    return CFINCOMPATIBLE;
  }
  return cf_opt_rhizome_peer_from_uri(rpeer, node->text);
}

int cf_fmt_rhizome_peer(struct cf_om_node **parentp, const struct config_rhizome_peer *rpeer)
{
  return cf_fmt_config_rhizome_peer(parentp, rpeer);
}

int cf_cmp_rhizome_peer(const struct config_rhizome_peer *a, const struct config_rhizome_peer *b)
{
  return cf_cmp_config_rhizome_peer(a, b);
}

int cf_opt_rhizome_peer_from_uri(struct config_rhizome_peer *rpeer, const char *text)
{
  const char *protocol;
  size_t protolen;
  const char *auth;
  if (str_is_uri(text)) {
    const char *hier;
    if (!(   str_uri_scheme(text, &protocol, &protolen)
	  && str_uri_hierarchical(text, &hier, NULL)
	  && str_uri_hierarchical_authority(hier, &auth, NULL))
    )
      return CFINVALID;
  } else {
    auth = text;
    protocol = "http";
    protolen = strlen(protocol);
  }
  const char *host;
  size_t hostlen;
  uint16_t port = HTTPD_PORT_DEFAULT;
  if (!str_uri_authority_hostname(auth, &host, &hostlen))
    return CFINVALID;
  str_uri_authority_port(auth, &port);
  if (protolen >= sizeof rpeer->protocol)
    return CFSTRINGOVERFLOW;
  if (hostlen >= sizeof rpeer->host)
    return CFSTRINGOVERFLOW;
  strncpy(rpeer->protocol, protocol, protolen)[protolen] = '\0';
  strncpy(rpeer->host, host, hostlen)[hostlen] = '\0';
  rpeer->port = port;
  return CFOK;
}

int cf_opt_str(char *str, size_t len, const char *text)
{
  if (strlen(text) >= len)
    return CFSTRINGOVERFLOW;
  strncpy(str, text, len);
  assert(str[len - 1] == '\0');
  return CFOK;
}

int cf_fmt_str(const char **textp, const char *str)
{
  *textp = str_edup(str);
  return CFOK;
}

int cf_cmp_str(const char *a, const char *b)
{
  return strcmp(a, b);
}

int cf_opt_str_nonempty(char *str, size_t len, const char *text)
{
  if (!text[0])
    return CFINVALID;
  return cf_opt_str(str, len, text);
}

int cf_fmt_str_nonempty(const char **textp, const char *str)
{
  if (!str[0])
    return CFINVALID;
  *textp = str_edup(str);
  return CFOK;
}

int cf_cmp_str_nonempty(const char *a, const char *b)
{
  return strcmp(a, b);
}

int cf_opt_int(int *intp, const char *text)
{
  const char *end = text;
  long value = strtol(text, (char**)&end, 10);
  if (end == text || *end)
    return CFINVALID;
  *intp = value;
  return CFOK;
}

int cf_fmt_int(const char **textp, const int *intp)
{
  char buf[22];
  sprintf(buf, "%d", *intp);
  *textp = str_edup(buf);
  return CFOK;
}

int cf_cmp_int(const int *a, const int *b)
{
  return *a < *b ? -1 : *a > *b ? 1 : 0;
}

int cf_opt_uint(unsigned int *uintp, const char *text)
{
  const char *end = text;
  unsigned long value = strtoul(text, (char**)&end, 10);
  if (end == text || *end)
    return CFINVALID;
  *uintp = value;
  return CFOK;
}

int cf_fmt_uint(const char **textp, const unsigned int *uintp)
{
  char buf[22];
  sprintf(buf, "%u", *uintp);
  *textp = str_edup(buf);
  return CFOK;
}

int cf_cmp_uint(const unsigned int *a, const unsigned int *b)
{
  return *a < *b ? -1 : *a > *b ? 1 : 0;
}

int cf_opt_int32_nonneg(int32_t *intp, const char *text)
{
  const char *end = text;
  long value = strtol(text, (char**)&end, 10);
  if (end == text || *end || value < 0 || value > 0x7fffffffL)
    return CFINVALID;
  *intp = value;
  return CFOK;
}

int cf_opt_int32_rs232baudrate(int32_t *intp, const char *text)
{
  const char *end = text;
  long value = strtol(text, (char**)&end, 10);
  if (end == text || *end || value < 0 || value > 0x7fffffffL)
    return CFINVALID;
  switch(value) {
  case 50: case 75: case 110: case 134: case 150: case 200: case 300:
  case 600: case 1200: case 1800: case 2400: case 4800: case 7200:
  case 9600: case 14400: case 28800: case 38400: case 57600: case 115200:
  case 230400:
    *intp = value;
    return CFOK;
    break;
  default:
    return CFINVALID;
  }
}

int cf_fmt_int32_rs232baudrate(const char **textp, const int32_t *intp)
{
  char buf[12];
  sprintf(buf, "%d", *intp);
  *textp = str_edup(buf);
  return CFOK;
}

int cf_cmp_int32_rs232baudrate(const int32_t *a, const int32_t *b)
{
  return *a < *b ? -1 : *a > *b ? 1 : 0;
}


static int cf_fmt_int32(const char **textp, const int32_t *intp)
{
  char buf[12];
  int n = sprintf(buf, "%d", *intp);
  *textp = strn_edup(buf, n);
  return CFOK;
}

int cf_cmp_int32(const int32_t *a, const int32_t *b)
{
  return *a < *b ? -1 : *a > *b ? 1 : 0;
}

static int cf_fmt_uint32(const char **textp, const uint32_t *uintp)
{
  char buf[12];
  int n = sprintf(buf, "%u", *uintp);
  *textp = strn_edup(buf, n);
  return CFOK;
}

int cf_cmp_uint32(const uint32_t *a, const uint32_t *b)
{
  return *a < *b ? -1 : *a > *b ? 1 : 0;
}

int cf_fmt_int32_nonneg(const char **textp, const int32_t *intp)
{
  if (*intp < 0)
    return CFINVALID;
  return cf_fmt_int32(textp, intp);
}

int cf_cmp_int32_nonneg(const int32_t *a, const int32_t *b)
{
  return cf_cmp_int32(a, b);
}

int cf_opt_uint32_nonzero(uint32_t *intp, const char *text)
{
  const char *end = text;
  unsigned long value = strtoul(text, (char**)&end, 10);
  if (end == text || *end || value < 1 || value > 0xffffffffL)
    return CFINVALID;
  *intp = value;
  return CFOK;
}

int cf_fmt_uint32_nonzero(const char **textp, const uint32_t *uintp)
{
  if (*uintp == 0)
    return CFINVALID;
  return cf_fmt_uint32(textp, uintp);
}

int cf_cmp_uint32_nonzero(const uint32_t *a, const uint32_t *b)
{
  return cf_cmp_uint32(a, b);
}

int cf_opt_uint32_time_interval(uint32_t *intp, const char *text)
{
  const char *t = text;
  uint32_t seconds = 0;
  while (*t) {
    const char *p = t;
    while (isdigit(*p))
      ++p;
    if (*p == '.' && (p != t || isdigit(p[1])))
      for (++p; isdigit(*p); ++p)
	;
    if (p == t)
      return CFINVALID;
    const char *end = t;
    double d = strtod(t, (char**)&end);
    if (end != p)
      return CFINVALID;
    switch (*p) {
      case 's': case 'S': ++p; break;
      case 'm': case 'M': d *= 60; ++p; break;
      case 'h': case 'H': d *= 60 * 60; ++p; break;
      case 'd': case 'D': d *= 60 * 60 * 24; ++p; break;
      case 'w': case 'W': d *= 60 * 60 * 24 * 7; ++p; break;
      case '\0': break;
      default: return CFINVALID;
    }
    if (d != floor(d))
      return CFINVALID;
    seconds += d;
    t = p;
  }
  *intp = seconds;
  return CFOK;
}

int cf_fmt_uint32_time_interval(const char **textp, const uint32_t *uintp)
{
  strbuf b = strbuf_alloca(60);
  uint32_t seconds = *uintp;
  if (seconds >= 7 * 24 * 60 * 60) {
    unsigned weeks = seconds / (7 * 24 * 60 * 60);
    seconds = seconds - weeks * (7 * 24 * 60 * 60);
    strbuf_sprintf(b, "%uw", weeks);
  }
  if (seconds >= 24 * 60 * 60) {
    unsigned days = seconds / (24 * 60 * 60);
    seconds = seconds - days * (24 * 60 * 60);
    strbuf_sprintf(b, "%ud", days);
  }
  if (seconds >= 60 * 60) {
    unsigned hours = seconds / (60 * 60);
    seconds = seconds - hours * (60 * 60);
    strbuf_sprintf(b, "%uh", hours);
  }
  if (seconds >= 60) {
    unsigned minutes = seconds / 60;
    seconds = seconds - minutes * 60;
    strbuf_sprintf(b, "%um", minutes);
  }
  if (seconds)
    strbuf_sprintf(b, "%us", seconds);
  if (strbuf_overrun(b))
    return CFINVALID;
  *textp = str_edup(strbuf_str(b));
  return CFOK;
}

int cf_cmp_uint32_time_interval(const uint32_t *a, const uint32_t *b)
{
  return cf_cmp_uint32(a, b);
}

int cf_opt_uint32_scaled(uint32_t *intp, const char *text)
{
  uint32_t result;
  const char *end;
  if (!str_to_uint32_scaled(text, 10, &result, &end) || *end)
    return CFINVALID;
  *intp = result;
  return CFOK;
}

int cf_fmt_uint32_scaled(const char **textp, const uint32_t *uintp)
{
  strbuf sb = strbuf_alloca(25);
  strbuf_append_uint32_scaled(sb, *uintp);
  assert(!strbuf_overrun(sb));
  *textp = str_edup(strbuf_str(sb));
  return CFOK;
}

int cf_cmp_uint32_scaled(const uint32_t *a, const uint32_t *b)
{
  return *a < *b ? -1 : *a > *b ? 1 : 0;
}

int cf_opt_uint64_scaled(uint64_t *intp, const char *text)
{
  uint64_t result;
  const char *end;
  if (!str_to_uint64_scaled(text, 10, &result, &end) || *end)
    return CFINVALID;
  *intp = result;
  return CFOK;
}

int cf_fmt_uint64_scaled(const char **textp, const uint64_t *uintp)
{
  strbuf sb = strbuf_alloca(25);
  strbuf_append_uint64_scaled(sb, *uintp);
  assert(!strbuf_overrun(sb));
  *textp = str_edup(strbuf_str(sb));
  return CFOK;
}

int cf_cmp_uint64_scaled(const uint64_t *a, const uint64_t *b)
{
  return *a < *b ? -1 : *a > *b ? 1 : 0;
}

int cf_opt_ushort(unsigned short *ushortp, const char *text)
{
  const char *end = text;
  unsigned long value = strtoul(text, (char**)&end, 10);
  if (end == text || *end || value > 0xffffL)
    return CFINVALID;
  *ushortp = value;
  return CFOK;
}

int cf_fmt_ushort(const char **textp, const unsigned short *ushortp)
{
  char buf[12];
  int n=sprintf(buf, "%u", (unsigned int) *ushortp);
  *textp = strn_edup(buf, n);
  return CFOK;
}

int cf_cmp_ushort(const unsigned short *a, const unsigned short *b)
{
  return *a < *b ? -1 : *a > *b ? 1 : 0;
}

int cf_opt_ushort_nonzero(unsigned short *ushortp, const char *text)
{
  unsigned short value;
  if (cf_opt_ushort(&value, text) != CFOK || value == 0)
    return CFINVALID;
  *ushortp = value;
  return CFOK;
}

int cf_fmt_ushort_nonzero(const char **textp, const unsigned short *ushortp)
{
  if (*ushortp == 0)
    return CFINVALID;
  return cf_fmt_ushort(textp, ushortp);
}

int cf_cmp_short(const short *a, const short *b)
{
  return *a < *b ? -1 : *a > *b ? 1 : 0;
}

int cf_cmp_ushort_nonzero(const unsigned short *a, const unsigned short *b)
{
  return cf_cmp_ushort(a, b);
}

int vld_argv(const struct cf_om_node *parent, struct config_argv *array, int result)
{
  unsigned short last_key = 0;
  unsigned i;
  if (array->ac) {
    unsigned short last_key = array->av[0].key;
    for (i = 1; i < array->ac; ++i) {
      unsigned short key = array->av[i].key;
      if (last_key > key) {
	cf_warn_node(parent, NULL, "array is not sorted");
	return CFERROR;
      }
      last_key = key;
    }
  }
  for (i = 0; i < array->ac; ++i) {
    unsigned short key = array->av[i].key;
    assert(key >= 1);
    assert(key >= last_key);
    if (last_key == key) {
      char labelkey[12];
      sprintf(labelkey, "%u", last_key);
      cf_warn_duplicate_node(parent, labelkey);
      result |= CFDUPLICATE;
    }
    while (++last_key < key && last_key <= sizeof(array->av)) {
      char labelkey[12];
      sprintf(labelkey, "%u", last_key);
      cf_warn_missing_node(parent, labelkey);
      result |= CFINCOMPLETE;
    }
    last_key = key;
  }
  return result;
}

int cf_opt_in_addr(struct in_addr *addrp, const char *text)
{
  struct in_addr addr;
  if (!inet_aton(text, &addr))
    return CFINVALID;
  *addrp = addr;
  return CFOK;
}

int cf_fmt_in_addr(const char **textp, const struct in_addr *addrp)
{
  *textp = str_edup(inet_ntoa(*addrp));
  return CFOK;
}

int cf_cmp_in_addr(const struct in_addr *a, const struct in_addr *b)
{
  return memcmp(a, b, sizeof(struct in_addr));
}

int cf_opt_uint16(uint16_t *uintp, const char *text)
{
  uint16_t ui = 0;
  const char *p;
  for (p = text; isdigit(*p); ++p) {
      uint16_t oui = ui;
      ui = ui * 10 + *p - '0';
      if (ui / 10 != oui)
	break;
  }
  if (*p)
    return CFINVALID;
  *uintp = ui;
  return CFOK;
}

int cf_fmt_uint16(const char **textp, const uint16_t *uintp)
{
  char buf[12];
  int n = sprintf(buf, "%u", (unsigned int) *uintp);
  *textp = strn_edup(buf, n);
  return CFOK;
}

int cf_cmp_uint16(const uint16_t *a, const uint16_t *b)
{
  return *a < *b ? -1 : *a > *b ? 1 : 0;
}

int cf_opt_uint16_nonzero(uint16_t *uintp, const char *text)
{
  uint16_t ui;
  if (cf_opt_uint16(&ui, text) != CFOK || ui == 0)
    return CFINVALID;
  *uintp = ui;
  return CFOK;
}

int cf_fmt_uint16_nonzero(const char **textp, const uint16_t *uintp)
{
  if (*uintp == 0)
    return CFINVALID;
  return cf_fmt_uint16(textp, uintp);
}

int cf_cmp_uint16_nonzero(const uint16_t *a, const uint16_t *b)
{
  return cf_cmp_uint16(a, b);
}

int cf_opt_sid(sid_t *sidp, const char *text)
{
  if (strcasecmp(text, "broadcast")==0){
    *sidp = SID_BROADCAST;
    return CFOK;
  }
  if (!str_is_subscriber_id(text))
    return CFINVALID;
  int r = str_to_sid_t(sidp, text);
  assert(r != -1);
  return CFOK;
}

int cf_fmt_sid(const char **textp, const sid_t *sidp)
{
  *textp = str_edup(alloca_tohex_sid_t(*sidp));
  return CFOK;
}

int cf_cmp_sid(const sid_t *a, const sid_t *b)
{
  return memcmp(a->binary, b->binary, sizeof a->binary);
}

int cf_opt_rhizome_bk(rhizome_bk_t *bkp, const char *text)
{
  return str_to_rhizome_bk_t(bkp, text) ? CFOK : CFINVALID;
}

int cf_fmt_rhizome_bk(const char **textp, const rhizome_bk_t *bkp)
{
  *textp = str_edup(alloca_tohex_rhizome_bk_t(*bkp));
  return CFOK;
}

int cf_cmp_rhizome_bk(const rhizome_bk_t *a, const rhizome_bk_t *b)
{
  return memcmp(a, b, sizeof a->binary);
}

int cf_opt_interface_type(short *typep, const char *text)
{
  if (strcasecmp(text, "ethernet") == 0) {
    *typep = OVERLAY_INTERFACE_ETHERNET;
    return CFOK;
  }
  if (strcasecmp(text, "wifi") == 0) {
    *typep = OVERLAY_INTERFACE_WIFI;
    return CFOK;
  }
  if (strcasecmp(text, "catear") == 0) {
    *typep = OVERLAY_INTERFACE_PACKETRADIO;
    return CFOK;
  }
  if (strcasecmp(text, "any") == 0) {
    *typep = OVERLAY_INTERFACE_ANY;
    return CFOK;
  }
  if (strcasecmp(text, "other") == 0) {
    *typep = OVERLAY_INTERFACE_OTHER;
    return CFOK;
  }
  if (strcasecmp(text, "unknown") == 0) {
    *typep = OVERLAY_INTERFACE_UNKNOWN;
    return CFOK;
  }
  return CFINVALID;
}

const char * interface_type_name(short type){
  switch (type) {
    case OVERLAY_INTERFACE_ETHERNET:	return "ethernet";
    case OVERLAY_INTERFACE_WIFI:	return "wifi";
    case OVERLAY_INTERFACE_PACKETRADIO: return "catear";
    case OVERLAY_INTERFACE_ANY:		return "any";
    case OVERLAY_INTERFACE_OTHER:	return "other";
    case OVERLAY_INTERFACE_UNKNOWN:	return "unknown";
  }
  return NULL;
}

int cf_fmt_interface_type(const char **textp, const short *typep)
{
  const char *t = interface_type_name(*typep);
  if (!t)
    return CFINVALID;
  *textp = str_edup(t);
  return CFOK;
}

int cf_opt_radio_type(short *typep, const char *text)
{
  if (strcasecmp(text, "rfd900") == 0) {
    *typep = RADIO_TYPE_RFD900;
    return CFOK;
  }
  if (strcasecmp(text, "rfm69") == 0) {
    *typep = RADIO_TYPE_RFM69;
    return CFOK;
  }
  return CFINVALID;
}

int cf_fmt_radio_type(const char **textp, const short *typep)
{
  const char *t = NULL;
  switch (*typep) {
    case RADIO_TYPE_RFD900:	t = "rfd900"; break;
    case RADIO_TYPE_RFM69:	t = "rfm69"; break;
  }
  if (!t)
    return CFINVALID;
  *textp = str_edup(t);
  return CFOK;
}

int cf_cmp_radio_type(const short *a, const short *b)
{
  return *a < *b ? -1 : *a > *b ? 1 : 0;
}

int cf_cmp_interface_type(const short *a, const short *b)
{
  return *a < *b ? -1 : *a > *b ? 1 : 0;
}

int cf_opt_socket_type(short *typep, const char *text)
{
  if (strcasecmp(text, "dgram") == 0) {
    *typep = SOCK_DGRAM;
    return CFOK;
  }
  if (strcasecmp(text, "stream") == 0) {
    *typep = SOCK_STREAM;
    return CFOK;
  }
  if (strcasecmp(text, "file") == 0) {
    *typep = SOCK_FILE;
    return CFOK;
  }
  if (strcasecmp(text, "external") == 0) {
    *typep = SOCK_EXT;
    return CFOK;
  }
  return CFINVALID;
}

int cf_fmt_socket_type(const char **textp, const short *typep)
{
  const char *t = NULL;
  switch (*typep) {
    case SOCK_DGRAM:  t = "dgram"; break;
    case SOCK_STREAM: t = "stream"; break;
    case SOCK_FILE:   t = "file"; break;
  }
  if (!t)
    return CFINVALID;
  *textp = str_edup(t);
  return CFOK;
}

int cf_cmp_socket_type(const short *a, const short *b)
{
  return cf_cmp_short(a, b);
}

int cf_opt_encapsulation(short *encapp, const char *text)
{
  if (strcasecmp(text, "overlay") == 0) {
    *encapp = ENCAP_OVERLAY;
    return CFOK;
  }
  if (strcasecmp(text, "single") == 0) {
    *encapp = ENCAP_SINGLE;
    return CFOK;
  }
  return CFINVALID;
}

int cf_fmt_encapsulation(const char **textp, const short *encapp)
{
  const char *t = NULL;
  switch (*encapp) {
    case ENCAP_OVERLAY: t = "overlay"; break;
    case ENCAP_SINGLE:  t = "single"; break;
  }
  if (!t)
    return CFINVALID;
  *textp = str_edup(t);
  return CFOK;
}

int cf_cmp_encapsulation(const short *a, const short *b)
{
  return cf_cmp_short(a, b);
}

int cf_opt_pattern_list(struct pattern_list *listp, const char *text)
{
  struct pattern_list list;
  memset(&list, 0, sizeof list);
  const char *word = NULL;
  const char *p;
  for (p = text; ; ++p) {
    if (!*p || isspace(*p) || *p == ',') {
      if (word) {
	size_t len = p - word;
	if (list.patc >= NELS(list.patv) || len >= sizeof(list.patv[list.patc]))
	  return CFARRAYOVERFLOW;
	strncpy(list.patv[list.patc++], word, len)[len] = '\0';
	word = NULL;
      }
      if (!*p)
	break;
    } else if (!word)
      word = p;
  }
  assert(word == NULL);
  if (list.patc == 0)
    return CFEMPTY;
  *listp = list;
  return CFOK;
}

int cf_fmt_pattern_list(const char **textp, const struct pattern_list *listp)
{
  if (listp->patc == 0)
    return CFEMPTY;
  char buf[sizeof listp->patv];
  char *bufp = buf;
  unsigned i;
  for (i = 0; i < listp->patc; ++i) {
    if (bufp != buf)
      *bufp++ = ',';
    const char *patvp = listp->patv[i];
    const char *npatvp = listp->patv[i + 1];
    while (bufp < &buf[sizeof buf - 1] && patvp < npatvp && (*bufp = *patvp))
      ++bufp, ++patvp;
    if (patvp >= npatvp)
      return CFINVALID;
    assert(bufp < &buf[sizeof buf - 1]);
  }
  *bufp = '\0';
  *textp = str_edup(buf);
  return CFOK;
}

int cf_cmp_pattern_list(const struct pattern_list *a, const struct pattern_list *b)
{
  unsigned i;
  for (i = 0; i < a->patc && i < b->patc; ++i) {
    int c = strcmp(a->patv[i], b->patv[i]);
    if (c)
      return c;
  }
  return (a->patc < b->patc) ? -1 : (a->patc > b->patc) ? 1 : 0;
}

/* Config parse function.  Implements the original form of the 'interfaces' config option.  Parses a
 * single text string of the form:
 *
 *   ( "+" | "-" ) [ interfacename ] [ "=" type ] [ ":" port [ ":" speed ] ]
 *
 * where:
 *
 *   "+" means include the interface
 *   "-" means exclude the interface
 *
 *   The original implementation applied include/exclude matching in the order that the list was
 *   given, but the new implementation applies all exclusions before apply inclusions.  This should
 *   not be a problem, as there were no known uses that depended on testing an inclusion before an
 *   exclusion.
 *
 *   An empty 'interfacename' matches all interfaces.  So a "+" by itself includes all interfaces,
 *   and a '-' by itself excludes all interfaces.  These two rules are applied after all other
 *   interface inclusions/exclusions are tested, otherwise "-" would overrule all other interfaces.
 *
 *   The optional 'type' tells DNA how to handle the interface in terms of bandwidth:distance
 *   relationship for calculating tick times etc.
 *
 *   The optional 'port' is the port number to bind all interfaces, instead of the default.
 *
 *   The optional 'speed' is the nominal bits/second bandwidth of the interface, instead of the
 *   default.  It is expressed as a positive integer with an optional scaling suffix, eg, "150k",
 *   "1K", "900M".
 *
 * @author Andrew Bettison <andrew@servalproject.com>
 */
static int cf_opt_network_interface_legacy(struct config_network_interface *nifp, const char *text)
{
  //DEBUGF(config, "%s text=%s", __FUNCTION__, alloca_str_toprint(text));
  struct config_network_interface nif;
  cf_dfl_config_network_interface(&nif);
  if (text[0] != '+' && text[0] != '-')
    return CFINVALID; // "Sign must be + or -"
  nif.exclude = (text[0] == '-');
  const char *const endtext = text + strlen(text);
  const char *name = text + 1;
  const char *p = strpbrk(name, "=:");
  if (!p)
    p = endtext;
  size_t len = p - name;
  if (name[0] == '>') {
    if (len - 1 >= sizeof(nif.file))
      return CFSTRINGOVERFLOW;
    strncpy(nif.file, &name[1], len - 1)[len - 1] = '\0';
    nif.match.patc = 0;
    nif.socket_type = SOCK_FILE;
  } else {
    int addstar = strnchr(name, len, '*') == NULL ? 1 : 0;
    if (len + addstar >= sizeof(nif.match.patv[0]))
      return CFSTRINGOVERFLOW;
    strncpy(nif.match.patv[0], name, len)[len + addstar] = '\0';
    if (addstar)
      nif.match.patv[0][len] = '*';
    nif.match.patc = 1;
    nif.socket_type = SOCK_DGRAM;
  }
  if (*p == '=') {
    const char *const type = p + 1;
    p = strchr(type, ':');
    if (!p)
      p = endtext;
    len = p - type;
    if (len) {
      char buf[len + 1];
      strncpy(buf, type, len)[len] = '\0';
      int result = cf_opt_interface_type(&nif.type, buf);
      switch (result) {
      case CFERROR: return CFERROR;
      case CFOK: break;
      default: return result; // "Invalid interface type"
      }
    }
  }
  if (*p == ':') {
    const char *const port = p + 1;
    p = strchr(port, ':');
    if (!p)
      p = endtext;
    len = p - port;
    if (len) {
      char buf[len + 1];
      strncpy(buf, port, len)[len] = '\0';
      int result = cf_opt_uint16_nonzero(&nif.port, buf);
      switch (result) {
      case CFERROR: return CFERROR;
      case CFOK: break;
      default: return result; // "Invalid interface port number"
      }
    }
  }
  if (*p == ':') {
    p = endtext;
  }
  if (*p)
    return CFINVALID; // "Extra junk at end of interface specification"
  *nifp = nif;
  return CFOK;
}

int cf_opt_network_interface(struct config_network_interface *nifp, const struct cf_om_node *node)
{
  if (!node->text)
    return cf_opt_config_network_interface(nifp, node);
  if (node->nodc) {
    cf_warn_incompatible_children(node);
    return CFINCOMPATIBLE;
  }
  return cf_opt_network_interface_legacy(nifp, node->text);
}

int cf_fmt_network_interface(struct cf_om_node **parentp, const struct config_network_interface *nifp)
{
  return cf_fmt_config_network_interface(parentp, nifp);
}

int cf_cmp_network_interface(const struct config_network_interface *a, const struct config_network_interface *b)
{
  return cf_cmp_config_network_interface(a, b);
}

int vld_network_interface(const struct cf_om_node *parent, struct config_network_interface *nifp, int result)
{
  if (nifp->match_type == OVERLAY_INTERFACE_UNKNOWN){
    int nodei_match_type = cf_om_get_child(parent, "match_type", NULL);
    assert(nodei_match_type != -1);
    cf_warn_node_value(parent->nodv[nodei_match_type], CFINVALID);
    return result | CFSUB(CFINVALID);
  }
  if (nifp->type == OVERLAY_INTERFACE_ANY
  || nifp->type == OVERLAY_INTERFACE_UNKNOWN){
    int nodei_type = cf_om_get_child(parent, "type", NULL);
    assert(nodei_type != -1);
    cf_warn_node_value(parent->nodv[nodei_type], CFINVALID);
    return result | CFSUB(CFINVALID);
  }
  if (nifp->match.patc != 0 && nifp->file[0]) {
    int nodei_match = cf_om_get_child(parent, "match", NULL);
    int nodei_file = cf_om_get_child(parent, "file", NULL);
    assert(nodei_match != -1);
    assert(nodei_file != -1);
    cf_warn_incompatible(parent->nodv[nodei_match], parent->nodv[nodei_file]);
    return result | CFSUB(CFINCOMPATIBLE);
  }
  if (nifp->socket_type == SOCK_UNSPECIFIED) {
    if (nifp->match.patc != 0)
      nifp->socket_type = SOCK_DGRAM;
    else if (nifp->file[0])
      nifp->socket_type = SOCK_FILE;
    else {
      cf_warn_missing_node(parent, "match");
      return result | CFINCOMPLETE;
    }
  } else {
    if (nifp->socket_type != SOCK_DGRAM && nifp->socket_type != SOCK_EXT && !nifp->file[0]){
      cf_warn_missing_node(parent, "file");
      return result | CFSUB(CFINCOMPATIBLE);
    }
    if (nifp->socket_type != SOCK_FILE){
      int nodei_drop=-1;
      if (nifp->drop_packets)
	nodei_drop=cf_om_get_child(parent, "drop_packets", NULL);
      if (nifp->broadcast.drop)
	nodei_drop=cf_om_get_child(parent, "broadcast", NULL);
      if (nifp->unicast.drop)
	nodei_drop=cf_om_get_child(parent, "unicast", NULL);
      if (nodei_drop!=-1){
	int nodei_socket_type=cf_om_get_child(parent, "socket_type", NULL);
	cf_warn_incompatible(parent->nodv[nodei_socket_type], parent->nodv[nodei_drop]);
	return result | CFSUB(CFINCOMPATIBLE);
      }
    }
  }
  return result;
}

/* Config parse function.  Implements the original form of the 'interfaces' config option.  Parses a
 * comma-separated list of interface rules (see cf_opt_network_interface_legacy() for the format of
 * each rule), then parses the regular config array-of-struct style interface option settings so
 * that both forms are supported.
 *
 * @author Andrew Bettison <andrew@servalproject.com>
 */
int cf_opt_interface_list(struct config_interface_list *listp, const struct cf_om_node *node)
{
  if (!node->text)
    return cf_opt_config_interface_list(listp, node);
  if (node->nodc) {
    cf_warn_incompatible_children(node);
    return CFINCOMPATIBLE;
  }
  const char *p;
  const char *arg = NULL;
  unsigned n = listp->ac;
  int result = CFOK;
  for (p = node->text; n < NELS(listp->av); ++p) {
    if (*p == '\0' || *p == ',' || isspace(*p)) {
      if (arg) {
	int len = p - arg;
	if (len > 80) {
	  result |= CFSTRINGOVERFLOW;
	  goto bye;
	}
	char buf[len + 1];
	strncpy(buf, arg, len)[len] = '\0';
	int ret = cf_opt_network_interface_legacy(&listp->av[n].value, buf);
	switch (ret) {
	case CFERROR: return CFERROR;
	case CFOK:
	  listp->av[n].key = n;
	  ++n;
	  break;
	default: {
	    strbuf b = strbuf_alloca(180);
	    strbuf_cf_flag_reason(b, ret);
	    cf_warn_node(node, NULL, "invalid interface rule %s -- %s", alloca_str_toprint(buf), strbuf_str(b)); \
	    result |= CFSUB(ret);
	    break;
	  }
	}
	arg = NULL;
      }
      if (!*p)
	break;
    } else if (!arg)
      arg = p;
  }
  if (*p) {
    result |= CFARRAYOVERFLOW;
    goto bye;
  }
  assert(n <= NELS(listp->av));
  listp->ac = n;
bye:
  if (listp->ac == 0)
    result |= CFEMPTY;
  return result;
}

int cf_fmt_interface_list(struct cf_om_node **parentp, const struct config_interface_list *listp)
{
  return cf_fmt_config_interface_list(parentp, listp);
}

int cf_cmp_interface_list(const struct config_interface_list *a, const struct config_interface_list *b)
{
  return cf_cmp_config_interface_list(a, b);
}

int cf_opt_log_level(int *levelp, const char *text)
{
  int level = string_to_log_level(text);
  if (level == LOG_LEVEL_INVALID)
    return CFINVALID;
  *levelp = level;
  return CFOK;
}

int cf_fmt_log_level(const char **textp, const int *levelp)
{
  const char *t = log_level_as_string(*levelp);
  if (!t)
    return CFINVALID;
  *textp = str_edup(t);
  return CFOK;
}

int cf_cmp_log_level(const int *a, const int *b)
{
  return cf_cmp_int(a, b);
}

/* Config type: http_authorization_schema
 *
 * @author Andrew Bettison <andrew@servalproject.com>
 */

int cf_opt_http_authorization_scheme(enum http_authorization_scheme *schemap, const char *text)
{
  if (strcasecmp(text, "noauth") == 0) {
    *schemap = NOAUTH;
    return CFOK;
  }
  if (strcasecmp(text, "basic") == 0) {
    *schemap = BASIC;
    return CFOK;
  }
  return CFINVALID;
}

int cf_fmt_http_authorization_scheme(const char **textp, const enum http_authorization_scheme *schemap)
{
  const char *t = NULL;
  switch (*schemap) {
    case NOAUTH: t = "noauth"; break;
    case BASIC:	 t = "basic"; break;
  }
  if (!t)
    return CFINVALID;
  *textp = str_edup(t);
  return CFOK;
}

int cf_cmp_http_authorization_scheme(const enum http_authorization_scheme *a, const enum http_authorization_scheme *b)
{
  return *a < *b ? -1 : *a > *b ? 1 : 0;
}