/* Copyright (c) 2001 Matej Pfajfar.
 * Copyright (c) 2001-2004, Roger Dingledine.
 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
 * Copyright (c) 2007-2019, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/**
 * @file type_defs.c
 * @brief Definitions for various low-level configuration types.
 *
 * This module creates a number of var_type_def_t objects, to be used by
 * typedvar.c in manipulating variables.
 *
 * The types here are common types that can be implemented with Tor's
 * low-level functionality.  To define new types, see var_type_def_st.h.
 **/

#include "orconfig.h"
#include "lib/conf/conftypes.h"
#include "lib/confmgt/typedvar.h"
#include "lib/confmgt/type_defs.h"
#include "lib/confmgt/unitparse.h"

#include "lib/cc/compat_compiler.h"
#include "lib/conf/conftypes.h"
#include "lib/container/smartlist.h"
#include "lib/encoding/confline.h"
#include "lib/encoding/time_fmt.h"
#include "lib/log/escape.h"
#include "lib/log/log.h"
#include "lib/log/util_bug.h"
#include "lib/malloc/malloc.h"
#include "lib/string/parse_int.h"
#include "lib/string/printf.h"

#include "lib/confmgt/var_type_def_st.h"

#include <stddef.h>
#include <string.h>
#include <errno.h>

//////
// CONFIG_TYPE_STRING
// CONFIG_TYPE_FILENAME
//
// These two types are the same for now, but they have different names.
//
// Warning: For this type, the default value (NULL) and "" are considered
// different values.  That is generally risky, and best avoided for other
// types in the future.
//////

static int
string_parse(void *target, const char *value, char **errmsg,
             const void *params)
{
  (void)params;
  (void)errmsg;
  char **p = (char**)target;
  *p = tor_strdup(value);
  return 0;
}

static char *
string_encode(const void *value, const void *params)
{
  (void)params;
  const char **p = (const char**)value;
  return *p ? tor_strdup(*p) : NULL;
}

static void
string_clear(void *value, const void *params)
{
  (void)params;
  char **p = (char**)value;
  tor_free(*p); // sets *p to NULL.
}

static const var_type_fns_t string_fns = {
  .parse = string_parse,
  .encode = string_encode,
  .clear = string_clear,
};

/////
// CONFIG_TYPE_INT
// CONFIG_TYPE_POSINT
//
// These types are implemented as int, possibly with a restricted range.
/////

typedef struct int_type_params_t {
  int minval;
  int maxval;
} int_parse_params_t;

static const int_parse_params_t INT_PARSE_UNRESTRICTED = {
  .minval = INT_MIN,
  .maxval = INT_MAX,
};

static const int_parse_params_t INT_PARSE_POSINT = {
  .minval = 0,
  .maxval = INT_MAX,
};

static int
int_parse(void *target, const char *value, char **errmsg, const void *params)
{
  const int_parse_params_t *pp;
  if (params) {
    pp = params;
  } else {
    pp = &INT_PARSE_UNRESTRICTED;
  }
  int *p = target;
  int ok=0;
  *p = (int)tor_parse_long(value, 10, pp->minval, pp->maxval, &ok, NULL);
  if (!ok) {
    tor_asprintf(errmsg, "Integer %s is malformed or out of bounds.",
                 value);
    return -1;
  }
  return 0;
}

static char *
int_encode(const void *value, const void *params)
{
  (void)params;
  int v = *(int*)value;
  char *result;
  tor_asprintf(&result, "%d", v);
  return result;
}

static void
int_clear(void *value, const void *params)
{
  (void)params;
  *(int*)value = 0;
}

static bool
int_ok(const void *value, const void *params)
{
  const int_parse_params_t *pp = params;
  if (pp) {
    int v = *(int*)value;
    return pp->minval <= v && v <= pp->maxval;
  } else {
    return true;
  }
}

static const var_type_fns_t int_fns = {
  .parse = int_parse,
  .encode = int_encode,
  .clear = int_clear,
  .ok = int_ok,
};

/////
// CONFIG_TYPE_UINT64
//
// This type is an unrestricted u64.
/////

static int
uint64_parse(void *target, const char *value, char **errmsg,
             const void *params)
{
  (void)params;
  (void)errmsg;
  uint64_t *p = target;
  int ok=0;
  *p = tor_parse_uint64(value, 10, 0, UINT64_MAX, &ok, NULL);
  if (!ok) {
    tor_asprintf(errmsg, "Integer %s is malformed or out of bounds.",
                 value);
    return -1;
  }
  return 0;
}

static char *
uint64_encode(const void *value, const void *params)
{
  (void)params;
  uint64_t v = *(uint64_t*)value;
  char *result;
  tor_asprintf(&result, "%"PRIu64, v);
  return result;
}

static void
uint64_clear(void *value, const void *params)
{
  (void)params;
  *(uint64_t*)value = 0;
}

static const var_type_fns_t uint64_fns = {
  .parse = uint64_parse,
  .encode = uint64_encode,
  .clear = uint64_clear,
};

/////
// CONFIG_TYPE_INTERVAL
// CONFIG_TYPE_MSEC_INTERVAL
// CONFIG_TYPE_MEMUNIT
//
// These types are implemented using the config_parse_units() function.
// The intervals are stored as ints, whereas memory units are stored as
// uint64_ts.
/////

static int
units_parse_u64(void *target, const char *value, char **errmsg,
                const void *params)
{
  const unit_table_t *table = params;
  tor_assert(table);
  uint64_t *v = (uint64_t*)target;
  int ok=1;
  *v = config_parse_units(value, table, &ok);
  if (!ok) {
    *errmsg = tor_strdup("Provided value is malformed or out of bounds.");
    return -1;
  }
  return 0;
}

static int
units_parse_int(void *target, const char *value, char **errmsg,
               const void *params)
{
  const unit_table_t *table = params;
  tor_assert(table);
  int *v = (int*)target;
  int ok=1;
  uint64_t u64 = config_parse_units(value, table, &ok);
  if (!ok) {
    *errmsg = tor_strdup("Provided value is malformed or out of bounds.");
    return -1;
  }
  if (u64 > INT_MAX) {
    tor_asprintf(errmsg, "Provided value %s is too large", value);
    return -1;
  }
  *v = (int) u64;
  return 0;
}

static bool
units_ok_int(const void *value, const void *params)
{
  (void)params;
  int v = *(int*)value;
  return v >= 0;
}

static const var_type_fns_t memunit_fns = {
  .parse = units_parse_u64,
  .encode = uint64_encode, // doesn't use params
  .clear = uint64_clear, // doesn't use params
};

static const var_type_fns_t interval_fns = {
  .parse = units_parse_int,
  .encode = int_encode, // doesn't use params
  .clear = int_clear, // doesn't use params,
  .ok = units_ok_int // can't use int_ok, since that expects int params.
};

/////
// CONFIG_TYPE_DOUBLE
//
// This is a nice simple double.
/////

static int
double_parse(void *target, const char *value, char **errmsg,
             const void *params)
{
  (void)params;
  (void)errmsg;
  double *v = (double*)target;
  char *endptr=NULL;
  errno = 0;
  *v = strtod(value, &endptr);
  if (endptr == value || *endptr != '\0') {
    // Either there are no converted characters, or there were some characters
    // that didn't get converted.
    tor_asprintf(errmsg, "Could not convert %s to a number.", escaped(value));
    return -1;
  }
  if (errno == ERANGE) {
    // strtod will set errno to ERANGE on underflow or overflow.
    bool underflow = -.00001 < *v && *v < .00001;
    tor_asprintf(errmsg,
                 "%s is too %s to express as a floating-point number.",
                 escaped(value), underflow ? "small" : "large");
    return -1;
  }
  return 0;
}

static char *
double_encode(const void *value, const void *params)
{
  (void)params;
  double v = *(double*)value;
  char *result;
  tor_asprintf(&result, "%f", v);
  return result;
}

static void
double_clear(void *value, const void *params)
{
  (void)params;
  double *v = (double *)value;
  *v = 0.0;
}

static const var_type_fns_t double_fns = {
  .parse = double_parse,
  .encode = double_encode,
  .clear = double_clear,
};

/////
// CONFIG_TYPE_BOOL
// CONFIG_TYPE_AUTOBOOL
//
// These types are implemented as a case-insensitive string-to-integer
// mapping.
/////

typedef struct enumeration_table_t {
  const char *name;
  int value;
} enumeration_table_t;

static int
enum_parse(void *target, const char *value, char **errmsg,
           const void *params)
{
  const enumeration_table_t *table = params;
  int *p = (int *)target;
  for (; table->name; ++table) {
    if (!strcasecmp(value, table->name)) {
      *p = table->value;
      return 0;
    }
  }
  tor_asprintf(errmsg, "Unrecognized value %s.", value);
  return -1;
}

static char *
enum_encode(const void *value, const void *params)
{
  int v = *(const int*)value;
  const enumeration_table_t *table = params;
  for (; table->name; ++table) {
    if (v == table->value)
      return tor_strdup(table->name);
  }
  return NULL; // error.
}

static void
enum_clear(void *value, const void *params)
{
  int *p = (int*)value;
  const enumeration_table_t *table = params;
  tor_assert(table->name);
  *p = table->value;
}

static bool
enum_ok(const void *value, const void *params)
{
  int v = *(const int*)value;
  const enumeration_table_t *table = params;
  for (; table->name; ++table) {
    if (v == table->value)
      return true;
  }
  return false;
}

static const enumeration_table_t enum_table_bool[] = {
  { "0", 0 },
  { "1", 1 },
  { NULL, 0 },
};

static const enumeration_table_t enum_table_autobool[] = {
  { "0", 0 },
  { "1", 1 },
  { "auto", -1 },
  { NULL, 0 },
};

static const var_type_fns_t enum_fns = {
  .parse = enum_parse,
  .encode = enum_encode,
  .clear = enum_clear,
  .ok = enum_ok,
};

/////
// CONFIG_TYPE_ISOTIME
//
// This is a time_t, encoded in ISO8601 format.
/////

static int
time_parse(void *target, const char *value, char **errmsg,
           const void *params)
{
  (void) params;
  time_t *p = target;
  if (parse_iso_time(value, p) < 0) {
    tor_asprintf(errmsg, "Invalid time %s", escaped(value));
    return -1;
  }
  return 0;
}

static char *
time_encode(const void *value, const void *params)
{
  (void)params;
  time_t v = *(const time_t *)value;
  char *result = tor_malloc(ISO_TIME_LEN+1);
  format_iso_time(result, v);
  return result;
}

static void
time_clear(void *value, const void *params)
{
  (void)params;
  time_t *t = value;
  *t = 0;
}

static const var_type_fns_t time_fns = {
  .parse = time_parse,
  .encode = time_encode,
  .clear = time_clear,
};

/////
// CONFIG_TYPE_CSV
//
// This type is a comma-separated list of strings, stored in a smartlist_t.
// An empty list may be encoded either as an empty smartlist, or as NULL.
/////

static int
csv_parse(void *target, const char *value, char **errmsg,
          const void *params)
{
  (void)params;
  (void)errmsg;
  smartlist_t **sl = (smartlist_t**)target;
  *sl = smartlist_new();
  smartlist_split_string(*sl, value, ",",
                         SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
  return 0;
}

static char *
csv_encode(const void *value, const void *params)
{
  (void)params;
  const smartlist_t *sl = *(const smartlist_t **)value;
  if (! sl)
    return tor_strdup("");

  return smartlist_join_strings(*(smartlist_t**)value, ",", 0, NULL);
}

static void
csv_clear(void *value, const void *params)
{
  (void)params;
  smartlist_t **sl = (smartlist_t**)value;
  if (!*sl)
    return;
  SMARTLIST_FOREACH(*sl, char *, cp, tor_free(cp));
  smartlist_free(*sl); // clears pointer.
}

static const var_type_fns_t csv_fns = {
  .parse = csv_parse,
  .encode = csv_encode,
  .clear = csv_clear,
};

/////
// CONFIG_TYPE_CSV_INTERVAL
//
// This type used to be a list of time intervals, used to determine a download
// schedule.  Now, only the first interval counts: everything after the first
// comma is discarded.
/////

static int
legacy_csv_interval_parse(void *target, const char *value, char **errmsg,
                          const void *params)
{
  (void)params;
  /* We used to have entire smartlists here.  But now that all of our
   * download schedules use exponential backoff, only the first part
   * matters. */
  const char *comma = strchr(value, ',');
  const char *val = value;
  char *tmp = NULL;
  if (comma) {
    tmp = tor_strndup(val, comma - val);
    val = tmp;
  }

  int rv = units_parse_int(target, val, errmsg, &time_units);
  tor_free(tmp);
  return rv;
}

static const var_type_fns_t legacy_csv_interval_fns = {
  .parse = legacy_csv_interval_parse,
  .encode = int_encode,
  .clear = int_clear,
};

/////
// CONFIG_TYPE_LINELIST
// CONFIG_TYPE_LINELIST_S
// CONFIG_TYPE_LINELIST_V
//
// A linelist is a raw config_line_t list.  Order is preserved.
//
// The LINELIST type is used for homogeneous lists, where all the lines
// have the same key.
//
// The LINELIST_S and LINELIST_V types are used for the case where multiple
// lines of different keys are kept in a single list, to preserve their
// relative order.  The unified list is stored as a "virtual" variable whose
// type is LINELIST_V; the individual sublists are treated as variables of
// type LINELIST_S.
//
// A linelist may be fragile or non-fragile. Assigning a line to a fragile
// linelist replaces the list with the line.  If the line has the "APPEND"
// command set on it, or if the list is non-fragile, the line is appended.
// Either way, the new list is non-fragile.
/////

static int
linelist_kv_parse(void *target, const struct config_line_t *line,
                  char **errmsg, const void *params)
{
  (void)params;
  (void)errmsg;
  config_line_t **lines = target;

  if (*lines && (*lines)->fragile) {
    if (line->command == CONFIG_LINE_APPEND) {
      (*lines)->fragile = 0;
    } else {
      config_free_lines(*lines); // sets it to NULL
    }
  }

  config_line_append(lines, line->key, line->value);
  return 0;
}

static int
linelist_kv_virt_noparse(void *target, const struct config_line_t *line,
                         char **errmsg, const void *params)
{
  (void)target;
  (void)line;
  (void)params;
  *errmsg = tor_strdup("Cannot assign directly to virtual option.");
  return -1;
}

static struct config_line_t *
linelist_kv_encode(const char *key, const void *value,
                   const void *params)
{
  (void)key;
  (void)params;
  config_line_t *lines = *(config_line_t **)value;
  return config_lines_dup(lines);
}

static struct config_line_t *
linelist_s_kv_encode(const char *key, const void *value,
                     const void *params)
{
  (void)params;
  config_line_t *lines = *(config_line_t **)value;
  return config_lines_dup_and_filter(lines, key);
}

static void
linelist_clear(void *target, const void *params)
{
  (void)params;
  config_line_t **lines = target;
  config_free_lines(*lines); // sets it to NULL
}

static bool
linelist_eq(const void *a, const void *b, const void *params)
{
  (void)params;
  const config_line_t *lines_a = *(const config_line_t **)a;
  const config_line_t *lines_b = *(const config_line_t **)b;
  return config_lines_eq(lines_a, lines_b);
}

static int
linelist_copy(void *target, const void *value, const void *params)
{
  (void)params;
  config_line_t **ptr = (config_line_t **)target;
  const config_line_t *val = *(const config_line_t **)value;
  config_free_lines(*ptr);
  *ptr = config_lines_dup(val);
  return 0;
}

static void
linelist_mark_fragile(void *target, const void *params)
{
  (void)params;
  config_line_t **ptr = (config_line_t **)target;
  if (*ptr)
    (*ptr)->fragile = 1;
}

static const var_type_fns_t linelist_fns = {
  .kv_parse = linelist_kv_parse,
  .kv_encode = linelist_kv_encode,
  .clear = linelist_clear,
  .eq = linelist_eq,
  .copy = linelist_copy,
  .mark_fragile = linelist_mark_fragile,
};

static const var_type_fns_t linelist_v_fns = {
  .kv_parse = linelist_kv_virt_noparse,
  .kv_encode = linelist_kv_encode,
  .clear = linelist_clear,
  .eq = linelist_eq,
  .copy = linelist_copy,
  .mark_fragile = linelist_mark_fragile,
};

static const var_type_fns_t linelist_s_fns = {
  .kv_parse = linelist_kv_parse,
  .kv_encode = linelist_s_kv_encode,
  .clear = linelist_clear,
  .eq = linelist_eq,
  .copy = linelist_copy,
};

/////
// CONFIG_TYPE_ROUTERSET
//
// XXXX This type is not implemented here, since routerset_t is not available
// XXXX to this module.
/////

/////
// CONFIG_TYPE_OBSOLETE
//
// Used to indicate an obsolete option.
//
// XXXX This is not a type, and should be handled at a higher level of
// XXXX abstraction.
/////

static int
ignore_parse(void *target, const char *value, char **errmsg,
             const void *params)
{
  (void)target;
  (void)value;
  (void)errmsg;
  (void)params;
  // XXXX move this to a higher level, once such a level exists.
  log_warn(LD_GENERAL, "Skipping obsolete configuration option.");
  return 0;
}

static char *
ignore_encode(const void *value, const void *params)
{
  (void)value;
  (void)params;
  return NULL;
}

static const var_type_fns_t ignore_fns = {
  .parse = ignore_parse,
  .encode = ignore_encode,
};

/**
 * Table mapping conf_type_t values to var_type_def_t objects.
 **/
static const var_type_def_t type_definitions_table[] = {
  [CONFIG_TYPE_STRING] = { .name="String", .fns=&string_fns },
  [CONFIG_TYPE_FILENAME] = { .name="Filename", .fns=&string_fns },
  [CONFIG_TYPE_INT] = { .name="SignedInteger", .fns=&int_fns,
                        .params=&INT_PARSE_UNRESTRICTED },
  [CONFIG_TYPE_POSINT] = { .name="Integer", .fns=&int_fns,
                           .params=&INT_PARSE_POSINT },
  [CONFIG_TYPE_UINT64] = { .name="Integer", .fns=&uint64_fns, },
  [CONFIG_TYPE_MEMUNIT] = { .name="DataSize", .fns=&memunit_fns,
                            .params=&memory_units },
  [CONFIG_TYPE_INTERVAL] = { .name="TimeInterval", .fns=&interval_fns,
                             .params=&time_units },
  [CONFIG_TYPE_MSEC_INTERVAL] = { .name="TimeMsecInterval",
                                  .fns=&interval_fns,
                                  .params=&time_msec_units },
  [CONFIG_TYPE_DOUBLE] = { .name="Float", .fns=&double_fns, },
  [CONFIG_TYPE_BOOL] = { .name="Boolean", .fns=&enum_fns,
                         .params=&enum_table_bool },
  [CONFIG_TYPE_AUTOBOOL] = { .name="Boolean+Auto", .fns=&enum_fns,
                             .params=&enum_table_autobool },
  [CONFIG_TYPE_ISOTIME] = { .name="Time", .fns=&time_fns, },
  [CONFIG_TYPE_CSV] = { .name="CommaList", .fns=&csv_fns, },
  [CONFIG_TYPE_CSV_INTERVAL] = { .name="TimeInterval",
                                 .fns=&legacy_csv_interval_fns, },
  [CONFIG_TYPE_LINELIST] = { .name="LineList", .fns=&linelist_fns,
                             .flags=CFLG_NOREPLACE },
  /*
   * A "linelist_s" is a derived view of a linelist_v: inspecting
   * it gets part of a linelist_v, and setting it adds to the linelist_v.
   */
  [CONFIG_TYPE_LINELIST_S] = { .name="Dependent", .fns=&linelist_s_fns,
                               .flags=CFLG_NOREPLACE|
                               /* The operations we disable here are
                                * handled by the linelist_v. */
                               CFLG_NOCOPY|CFLG_NOCMP|CFLG_NODUMP },
  [CONFIG_TYPE_LINELIST_V] = { .name="Virtual", .fns=&linelist_v_fns,
                               .flags=CFLG_NOREPLACE|CFLG_NOSET },
  [CONFIG_TYPE_OBSOLETE] = {
         .name="Obsolete", .fns=&ignore_fns,
         .flags=CFLG_GROUP_OBSOLETE,
  }
};

/**
 * Return a pointer to the var_type_def_t object for the given
 * config_type_t value, or NULL if no such type definition exists.
 **/
const var_type_def_t *
lookup_type_def(config_type_t type)
{
  int t = type;
  tor_assert(t >= 0);
  if (t >= (int)ARRAY_LENGTH(type_definitions_table))
    return NULL;
  return &type_definitions_table[t];
}
