// -*- c-basic-offset: 4; related-file-name: "../include/click/confparse.hh" -*-
/*
 * confparse.{cc,hh} -- configuration string parsing
 * Eddie Kohler
 *
 * Copyright (c) 1999-2000 Massachusetts Institute of Technology
 * Copyright (c) 2000-2001 Mazu Networks, Inc.
 * Copyright (c) 2001-2003 International Computer Science Institute
 * Copyright (c) 2004-2006 Regents of the University of California
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>

#include <click/glue.hh>
#include <click/confparse.hh>
#include <click/error.hh>
#include <click/straccum.hh>
#include <click/ipaddress.hh>
#include <click/ipaddresslist.hh>
#include <click/etheraddress.hh>
#ifdef HAVE_IP6
# include <click/ip6address.hh>
# include <click/ip6flowid.hh>
#endif
#ifndef CLICK_TOOL
# include <click/router.hh>
# include <click/handlercall.hh>
# include <click/nameinfo.hh>
# include <click/standard/addressinfo.hh>
# define CP_CONTEXT_ARG , Element *context
# define CP_PASS_CONTEXT , context
#else
# include <click/hashmap.hh>
# include <click/timestamp.hh>
# define CP_CONTEXT_ARG
# define CP_PASS_CONTEXT
#endif
#ifdef CLICK_USERLEVEL
# include <pwd.h>
#endif
#include <stdarg.h>
CLICK_DECLS

int cp_errno;

const char *
cp_skip_space(const char *begin, const char *end)
{
    while (begin < end && isspace((unsigned char) *begin))
	begin++;
    return begin;
}

bool
cp_eat_space(String &str)
{
    const char *begin = str.begin(), *end = str.end();
    const char *space = cp_skip_space(begin, end);
    str = str.substring(space, end);
    return space != begin;
}

bool
cp_is_word(const String &str)
{
    const char *s = str.data();
    int len = str.length();
    for (int i = 0; i < len; i++)
	if (s[i] == '\"' || s[i] == '\'' || s[i] == '\\' || s[i] == ','
	    || s[i] <= 32 || s[i] >= 127)
	    return false;
    return len > 0;
}

bool
cp_is_click_id(const String &str)
{
  const unsigned char *s = reinterpret_cast<const unsigned char*>(str.data());
  int len = str.length();
  for (int i = 0; i < len; i++)
    if (isalnum(s[i]) || s[i] == '_' || s[i] == '@')
      /* character OK */;
    else if (s[i] != '/' || i == 0 || i == len - 1 || s[i+1] == '/')
      return false;
  return len > 0;
}

static int
xvalue(int x)
{
    if (x >= '0' && x <= '9')
	return x - '0';
    else if (x >= 'A' && x <= 'F')
	return x - 'A' + 10;
    else if (x >= 'a' && x <= 'f')
	return x - 'a' + 10;
    else
	return -1;
}

static const char *
skip_comment(const char *s, const char *end)
{
  assert(s + 1 < end && *s == '/' && (s[1] == '/' || s[1] == '*'));

  if (s[1] == '/') {
    for (s += 2; s + 1 < end && *s != '\n' && *s != '\r'; s++)
      /* nada */;
    if (s + 1 < end && *s == '\r' && s[1] == '\n')
      s++;
    return s + 1;
  } else { /* s[1] == '*' */
    for (s += 2; s + 2 < end && (*s != '*' || s[1] != '/'); s++)
      /* nada */;
    return s + 2;
  }
}

static const char *
skip_backslash(const char *s, const char *end)
{
  assert(s < end && *s == '\\');

  if (s + 1 >= end)
    return s + 1;
  else if (s[1] == '<') {
    for (s += 2; s < end; )
      if (*s == '>')
	return s + 1;
      else if (*s == '/' && s + 1 < end && (s[1] == '/' || s[1] == '*'))
	s = skip_comment(s, end);
      else
	s++;
    return s;
  } else if (s[1] == '\r' && s + 2 < end && s[2] == '\n')
    return s + 3;
  else
    return s + 2;
}

static const char *
skip_double_quote(const char *s, const char *end)
{
  assert(s < end && *s == '\"');

  for (s++; s < end; )
    if (*s == '\\')
      s = skip_backslash(s, end);
    else if (*s == '\"')
      return s + 1;
    else
      s++;

  return end;
}

static const char *
skip_single_quote(const char *s, const char *end)
{
  assert(s < end && *s == '\'');

  for (s++; s < end; s++)
    if (*s == '\'')
      return s + 1;

  return end;
}

const char *
cp_skip_comment_space(const char *begin, const char *end)
{
  for (; begin < end; begin++) {
    if (isspace((unsigned char) *begin))
      /* nada */;
    else if (*begin == '/' && begin + 1 < end && (begin[1] == '/' || begin[1] == '*'))
      begin = skip_comment(begin, end) - 1;
    else
      break;
  }
  return begin;
}

static String
partial_uncomment(const String &str, int i, int *comma_pos)
{
  const char *s = str.data() + i;
  const char *end = str.end();

  // skip initial spaces
  s = cp_skip_comment_space(s, end);

  // accumulate text, skipping comments
  StringAccum sa;
  const char *left = s;
  const char *right = s;
  bool closed = false;

  while (s < end) {
    if (isspace((unsigned char) *s))
      s++;
    else if (*s == '/' && s + 1 < end && (s[1] == '/' || s[1] == '*')) {
      s = skip_comment(s, end);
      closed = true;
    } else if (*s == ',' && comma_pos)
      break;
    else {
      if (closed) {
	sa << str.substring(left, right) << ' ';
	left = s;
	closed = false;
      }
      if (*s == '\'')
	s = skip_single_quote(s, end);
      else if (*s == '\"')
	s = skip_double_quote(s, end);
      else if (*s == '\\' && s + 1 < end && s[1] == '<')
	s = skip_backslash(s, end);
      else
	s++;
      right = s;
    }
  }

  if (comma_pos)
    *comma_pos = s - str.begin();
  if (!sa)
    return str.substring(left, right);
  else {
    sa << str.substring(left, right);
    return sa.take_string();
  }
}

String
cp_uncomment(const String &str)
{
  return partial_uncomment(str, 0, 0);
}

const char *
cp_process_backslash(const char *s, const char *end, StringAccum &sa)
{
  assert(s < end && *s == '\\');

  if (s == end - 1) {
    sa << '\\';
    return end;
  }
  
  switch (s[1]) {
    
   case '\r':
    return (s + 2 < end && s[2] == '\n' ? s + 3 : s + 2);

   case '\n':
    return s + 2;
    
   case 'a': sa << '\a'; return s + 2;
   case 'b': sa << '\b'; return s + 2;
   case 'f': sa << '\f'; return s + 2;
   case 'n': sa << '\n'; return s + 2;
   case 'r': sa << '\r'; return s + 2;
   case 't': sa << '\t'; return s + 2;
   case 'v': sa << '\v'; return s + 2;
    
   case '0': case '1': case '2': case '3':
   case '4': case '5': case '6': case '7': {
     int c = 0, d = 0;
     for (s++; s < end && *s >= '0' && *s <= '7' && d < 3; s++, d++)
       c = c*8 + *s - '0';
     sa << (char)c;
     return s;
   }
   
   case 'x': {
     int c = 0;
     for (s += 2; s < end; s++)
       if (*s >= '0' && *s <= '9')
	 c = c*16 + *s - '0';
       else if (*s >= 'A' && *s <= 'F')
	 c = c*16 + *s - 'A' + 10;
       else if (*s >= 'a' && *s <= 'f')
	 c = c*16 + *s - 'a' + 10;
       else
	 break;
     sa << (char)c;
     return s;
   }
   
   case '<': {
     int c = 0, d = 0;
     for (s += 2; s < end; s++) {
       if (*s == '>')
	 return s + 1;
       else if (*s >= '0' && *s <= '9')
	 c = c*16 + *s - '0';
       else if (*s >= 'A' && *s <= 'F')
	 c = c*16 + *s - 'A' + 10;
       else if (*s >= 'a' && *s <= 'f')
	 c = c*16 + *s - 'a' + 10;
       else if (*s == '/' && s + 1 < end && (s[1] == '/' || s[1] == '*')) {
	 s = skip_comment(s, end) - 1;
	 continue;
       } else
	 continue;	// space (ignore it) or random (error)
       if (++d == 2) {
	 sa << (char)c;
	 c = d = 0;
       }
     }
     // ran out of space in string
     return end;
   }

   case '\\': case '\'': case '\"': case '$':
   default:
    sa << s[1];
    return s + 2;
    
  }
}

String
cp_unquote(const String &in_str)
{
  String str = partial_uncomment(in_str, 0, 0);
  const char *s = str.data();
  const char *end = str.end();

  // accumulate a word
  StringAccum sa;
  const char *start = s;
  int quote_state = 0;

  for (; s < end; s++)
    switch (*s) {

     case '\"':
     case '\'':
      if (quote_state == 0) {
	sa << str.substring(start, s); // null string if start >= s
	start = s + 1;
	quote_state = *s;
      } else if (quote_state == *s) {
	sa << str.substring(start, s);
	start = s + 1;
	quote_state = 0;
      }
      break;
      
     case '\\':
      if (s + 1 < end && (quote_state == '\"'
			  || (quote_state == 0 && s[1] == '<'))) {
	sa << str.substring(start, s);
	start = cp_process_backslash(s, end, sa);
	s = start - 1;
      }
      break;
      
    }

  if (start == str.begin())
    return str;
  else {
    sa << str.substring(start, s);
    return sa.take_string();
  }
}

String
cp_quote(const String &str, bool allow_newlines)
{
  if (!str)
    return String("\"\"");
  
  const char *s = str.data();
  const char *end = str.end();
  
  StringAccum sa;
  const char *start = s;

  sa << '\"';
  
  for (; s < end; s++)
    switch (*s) {
      
     case '\\': case '\"': case '$':
      sa << str.substring(start, s) << '\\' << *s;
      start = s + 1;
      break;
      
     case '\t':
      sa << str.substring(start, s) << "\\t";
      start = s + 1;
      break;

     case '\r':
      sa << str.substring(start, s) << "\\r";
      start = s + 1;
      break;

     case '\n':
      if (!allow_newlines) {
	sa << str.substring(start, s) << "\\n";
	start = s + 1;
      }
      break;

     default:
      if ((unsigned char)*s < 32 || (unsigned char)*s >= 127) {
	unsigned u = (unsigned char)*s;
	sa << str.substring(start, s)
	   << '\\' << (char)('0' + (u >> 6))
	   << (char)('0' + ((u >> 3) & 7))
	   << (char)('0' + (u & 7));
	start = s + 1;
      }
      break;
      
    }
  
  sa << str.substring(start, s) << '\"';
  return sa.take_string();
}

void
cp_argvec(const String &conf, Vector<String> &args)
{
  // common case: no configuration
  int len = conf.length();
  if (len == 0)
    return;
  
  for (int pos = 0; pos < len; pos++) {
    String arg = partial_uncomment(conf, pos, &pos);
    // add the argument if it is nonempty or not the last argument
    if (arg || pos < len)
      args.push_back(arg);
  }
}

static const char *
skip_spacevec_item(const char *s, const char *end)
{
  while (s < end)
    switch (*s) {
      
     case '/':
      // a comment ends the item
      if (s + 1 < end && (s[1] == '/' || s[1] == '*'))
	return s;
      s++;
      break;
      
     case '\"':
      s = skip_double_quote(s, end);
      break;
      
     case '\'':
      s = skip_single_quote(s, end);
      break;

     case '\\':			// check for \<...> strings
      if (s + 1 < end && s[1] == '<')
	s = skip_backslash(s, end);
      else
	s++;
      break;
      
     case ' ':
     case '\f':
     case '\n':
     case '\r':
     case '\t':
     case '\v':
      return s;

     default:
      s++;
      break;
      
    }
  return s;
}

void
cp_spacevec(const String &conf, Vector<String> &vec)
{
  // common case: no configuration
  if (conf.length() == 0)
    return;

  // collect arguments with cp_pop_spacevec
  const char *s = conf.data();
  const char *end = conf.end();
  while ((s = cp_skip_comment_space(s, end)) < end) {
    const char *t = skip_spacevec_item(s, end);
    vec.push_back(conf.substring(s, t));
    s = t;
  }
}

String
cp_pop_spacevec(String &conf)
{
  const char *item = cp_skip_comment_space(conf.begin(), conf.end());
  const char *item_end = skip_spacevec_item(item, conf.end());
  String answer = conf.substring(item, item_end);
  item_end = cp_skip_comment_space(item_end, conf.end());
  conf = conf.substring(item_end, conf.end());
  return answer;
}

String
cp_unargvec(const Vector<String> &args)
{
  if (args.size() == 0)
    return String();
  else if (args.size() == 1)
    return args[0];
  else {
    StringAccum sa;
    sa << args[0];
    for (int i = 1; i < args.size(); i++)
      sa << ", " << args[i];
    return sa.take_string();
  }
}

String
cp_unspacevec(const String *begin, const String *end)
{
  StringAccum sa;
  for (; begin < end; begin++)
    sa << *begin << ' ';
  sa.pop_back();
  return sa.take_string();
}


// PARSING STRINGS

bool
cp_string(const String &str, String *return_value, String *rest)
{
  const char *s = str.data();
  const char *end = str.end();

  // accumulate a word
  while (s < end)
    switch (*s) {
      
     case ' ':
     case '\f':
     case '\n':
     case '\r':
     case '\t':
     case '\v':
      goto done;

     case '\"':
      s = skip_double_quote(s, end);
      break;

     case '\'':
      s = skip_single_quote(s, end);
      break;

     case '\\':
      if (s + 1 < end && s[1] == '<')
	s = skip_backslash(s, end);
      else
	s++;
      break;

     default:
      s++;
      break;
      
    }
  
 done:
  if (s == str.begin() || (!rest && s != end))
    return false;
  else {
    if (rest)
      *rest = str.substring(s, end);
    *return_value = cp_unquote(str.substring(str.begin(), s));
    return true;
  }
}

bool
cp_word(const String &str, String *return_value, String *rest)
{
  String word;
  if (!cp_string(str, &word, rest))
    return false;
  else if (!cp_is_word(word))
    return false;
  else {
    *return_value = word;
    return true;
  }
}

bool
cp_keyword(const String &str, String *return_value, String *rest)
{
  const char *s = str.data();
  const char *end = str.end();

  // accumulate a word
  for (; s < end; s++)
    switch (*s) {
      
     case ' ':
     case '\f':
     case '\n':
     case '\r':
     case '\t':
     case '\v':
      goto done;

      // characters allowed unquoted in keywords
     case '_':
     case '.':
     case ':':
      break;
      
     default:
      if (!isalnum((unsigned char) *s))
	return false;
      break;
      
    }
  
 done:
  if (s == str.begin() || (!rest && s < end))
    return false;
  else {
    *return_value = str.substring(str.begin(), s);
    if (rest) {
      for (; s < end; s++)
	if (!isspace((unsigned char) *s))
	  break;
      *rest = str.substring(s, end);
    }
    return true;
  }
}


// PARSING INTEGERS

bool
cp_bool(const String &str, bool *return_value)
{
  const char *s = str.data();
  int len = str.length();
  
  if (len == 1 && (s[0] == '0' || s[0] == 'n' || s[0] == 'f'))
    *return_value = false;
  else if (len == 1 && (s[0] == '1' || s[0] == 'y' || s[0] == 't'))
    *return_value = true;
  else if (len == 5 && memcmp(s, "false", 5) == 0)
    *return_value = false;
  else if (len == 4 && memcmp(s, "true", 4) == 0)
    *return_value = true;
  else if (len == 2 && memcmp(s, "no", 2) == 0)
    *return_value = false;
  else if (len == 3 && memcmp(s, "yes", 3) == 0)
    *return_value = true;
  else
    return false;

  return true;
}

const char *
cp_unsigned(const char *begin, const char *end, int base, uint32_t *return_value)
{
  const char *s = begin;
  if (s < end && *s == '+')
    s++;

  if ((base == 0 || base == 16) && s + 1 < end && *s == '0'
      && (s[1] == 'x' || s[1] == 'X')) {
    s += 2;
    base = 16;
  } else if (base == 0 && s < end && *s == '0')
    base = 8;
  else if (base == 0)
    base = 10;
  else if (base < 2 || base > 36) {
    cp_errno = CPE_INVALID;
    return begin;
  }

  if (s >= end || *s == '_')	// no digits or initial underscore
    return begin;

  uint32_t overflow_val = 0xFFFFFFFFU / base;
  int32_t overflow_digit = 0xFFFFFFFFU - (overflow_val * base);
  
  uint32_t val = 0;
  cp_errno = CPE_FORMAT;
  
  while (s < end) {
    // find digit
    int digit;
    if (*s >= '0' && *s <= '9')
      digit = *s - '0';
    else if (*s >= 'A' && *s <= 'Z')
      digit = *s - 'A' + 10;
    else if (*s >= 'a' && *s <= 'z')
      digit = *s - 'a' + 10;
    else if (*s == '_' && s + 1 < end && s[1] != '_')
      // skip underscores between digits
      goto next_digit;
    else
      digit = 36;
    if (digit >= base)
      break;
    else if (val == 0 && cp_errno == CPE_FORMAT)
      cp_errno = CPE_OK;
    // check for overflow
    if (val > overflow_val || (val == overflow_val && digit > overflow_digit))
      cp_errno = CPE_OVERFLOW;
    // assign new value
    val = val * base + digit;
   next_digit:
    s++;
  }

  if (cp_errno == CPE_FORMAT)
    return begin;
  else {
    *return_value = (cp_errno ? 0xFFFFFFFFU : val);
    return (s > begin && s[-1] == '_' ? s - 1 : s);
  }
}

bool cp_unsigned(const String &str, int base, uint32_t *return_value)
{
  uint32_t u;
  const char *s = cp_unsigned(str.begin(), str.end(), base, &u);
  if (s == str.end() && str.length()) {
    *return_value = u;
    return true;
  } else
    return false;
}

const char *
cp_integer(const char *begin, const char *end, int base, int32_t *return_value)
{
  const char *s = begin;
  bool negative = false;
  if (s + 1 < end && *s == '-' && s[1] != '+') {
    negative = true;
    s++;
  }

  uint32_t value;
  if ((end = cp_unsigned(s, end, base, &value)) == s)
    return begin;

  uint32_t max = (negative ? 0x80000000U : 0x7FFFFFFFU);
  if (value > max) {
    cp_errno = CPE_OVERFLOW;
    value = max;
  }

  *return_value = (negative ? -value : value);
  return end;
}

bool cp_integer(const String &str, int base, int32_t *return_value)
{
  int32_t i;
  const char *s = cp_integer(str.begin(), str.end(), base, &i);
  if (s == str.end() && str.length()) {
    *return_value = i;
    return true;
  } else
    return false;
}


#ifdef HAVE_INT64_TYPES

static uint64_t unsigned64_overflow_vals[] = { 0, 0, 9223372036854775807ULL, 6148914691236517205ULL, 4611686018427387903ULL, 3689348814741910323ULL, 3074457345618258602ULL, 2635249153387078802ULL, 2305843009213693951ULL, 2049638230412172401ULL, 1844674407370955161ULL, 1676976733973595601ULL, 1537228672809129301ULL, 1418980313362273201ULL, 1317624576693539401ULL, 1229782938247303441ULL, 1152921504606846975ULL, 1085102592571150095ULL, 1024819115206086200ULL, 970881267037344821ULL, 922337203685477580ULL, 878416384462359600ULL, 838488366986797800ULL, 802032351030850070ULL, 768614336404564650ULL, 737869762948382064ULL, 709490156681136600ULL, 683212743470724133ULL, 658812288346769700ULL, 636094623231363848ULL, 614891469123651720ULL, 595056260442243600ULL, 576460752303423487ULL, 558992244657865200ULL, 542551296285575047ULL, 527049830677415760ULL };

const char *
cp_unsigned(const char *begin, const char *end, int base, uint64_t *return_value)
{
  const char *s = begin;
  if (s < end && *s == '+')
    s++;

  if ((base == 0 || base == 16) && s + 1 < end && *s == '0'
      && (s[1] == 'x' || s[1] == 'X')) {
    s += 2;
    base = 16;
  } else if (base == 0 && *s == '0')
    base = 8;
  else if (base == 0)
    base = 10;
  else if (base < 2 || base > 36) {
    cp_errno = CPE_INVALID;
    return begin;
  }

  if (s >= end || *s == '_')	// no digits or initial underscore
    return begin;

  uint64_t overflow_val = unsigned64_overflow_vals[base];
  int64_t overflow_digit = 0xFFFFFFFFFFFFFFFFULL - (overflow_val * base);

  uint64_t val = 0;
  cp_errno = CPE_FORMAT;
  
  while (s < end) {
    // find digit
    int digit;
    if (*s >= '0' && *s <= '9')
      digit = *s - '0';
    else if (*s >= 'A' && *s <= 'Z')
      digit = *s - 'A' + 10;
    else if (*s >= 'a' && *s <= 'z')
      digit = *s - 'a' + 10;
    else if (*s == '_' && s + 1 < end && s[1] != '_')
      // skip underscores between digits
      goto next_digit;
    else
      digit = 36;
    if (digit >= base)
      break;
    else if (val == 0 && cp_errno == CPE_FORMAT)
      cp_errno = CPE_OK;
    // check for overflow
    if (val > overflow_val || (val == overflow_val && digit > overflow_digit))
      cp_errno = CPE_OVERFLOW;
    // assign new value
    val = val * base + digit;
   next_digit:
    s++;
  }

  if (cp_errno == CPE_FORMAT)
    return begin;
  else {
    *return_value = (cp_errno ? 0xFFFFFFFFFFFFFFFFULL : val);
    return (s > begin && s[-1] == '_' ? s - 1 : s);
  }
}

bool
cp_unsigned(const String &str, int base, uint64_t *return_value)
{
  uint64_t q;
  const char *s = cp_unsigned64(str.begin(), str.end(), base, &q);
  if (s == str.end() && str.length()) {
    *return_value = q;
    return true;
  } else
    return false;
}

const char *
cp_integer(const char *begin, const char *end, int base, int64_t *return_value)
{
  const char *s = begin;
  bool negative = false;
  if (s + 1 < end && *s == '-' && s[1] != '+') {
    negative = true;
    s++;
  }

  uint64_t value;
  if ((end = cp_unsigned(s, end, base, &value)) == s)
    return begin;

  uint64_t max = (negative ? 0x8000000000000000ULL : 0x7FFFFFFFFFFFFFFFULL);
  if (value > max) {
    cp_errno = CPE_OVERFLOW;
    value = max;
  }

  *return_value = (negative ? -value : value);
  return end;
}

bool
cp_integer(const String &str, int base, int64_t *return_value)
{
  int64_t q;
  const char *s = cp_integer(str.begin(), str.end(), base, &q);
  if (s == str.end() && str.length()) {
    *return_value = q;
    return true;
  } else
    return false;
}

#endif

#ifdef CLICK_USERLEVEL

bool
cp_file_offset(const String &str, off_t *return_value)
{
# if SIZEOF_OFF_T == 4
  return cp_unsigned(str, reinterpret_cast<uint32_t *>(return_value));
# elif SIZEOF_OFF_T == 8
  return cp_unsigned(str, reinterpret_cast<uint64_t *>(return_value));
# else
#  error "unexpected sizeof(off_t)"
# endif
}

#endif


// PARSING REAL NUMBERS

static uint32_t exp10val[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
			       10000000, 100000000, 1000000000 };

bool
cp_unsigned_real10(const String &str, int frac_digits, int exponent_delta,
		   uint32_t *return_int_part, uint32_t *return_frac_part)
{
  const char *s = str.data();
  const char *last = s + str.length();
  
  cp_errno = CPE_FORMAT;
  if (s == last)
    return false;
  if (frac_digits < 0 || frac_digits > 9) {
    cp_errno = CPE_INVALID;
    return false;
  }
  
  if (*s == '+')
    s++;
  
  // find integer part of string
  const char *int_s = s;
  for (int_s = s; s < last; s++)
    if (!(isdigit((unsigned char) *s) || (*s == '_' && s > int_s && s < last - 1 && s[1] != '_')))
      break;
  int int_chars = s - int_s;
  
  // find fractional part of string
  const char *frac_s;
  int frac_chars;
  if (s < last && *s == '.') {
    for (frac_s = ++s; s < last; s++)
      if (!(isdigit((unsigned char) *s) || (*s == '_' && s > frac_s && s < last - 1 && s[1] != '_')))
	break;
    frac_chars = s - frac_s;
  } else
    frac_s = s, frac_chars = 0;
  
  // no integer or fraction? illegal real
  if (int_chars == 0 && frac_chars == 0)
    return false;
  
  // find exponent, if any
  int exponent = 0;
  if (s < last && (*s == 'E' || *s == 'e')) {
    if (++s == last)
      return false;
    
    bool negexp = (*s == '-');
    if (*s == '-' || *s == '+')
      s++;
    if (s >= last || !isdigit((unsigned char) *s))
      return false;
    
    // XXX overflow?
    for (; s < last; s++)
      if (isdigit((unsigned char) *s))
	exponent = 10*exponent + *s - '0';
      else if (*s != '_' || s == last - 1 || s[1] == '_')
	break;
    
    if (negexp)
      exponent = -exponent;
  }
  
  if (s != last)
    return false;

  // OK! now create the result
  // determine integer part; careful about overflow
  uint32_t int_part = 0;
  cp_errno = CPE_OK;
  exponent += exponent_delta;
  int digit;
  
  for (int i = 0; i < int_chars + exponent; i++) {
    if (i < int_chars)
      digit = int_s[i] - '0';
    else if (i - int_chars < frac_chars)
      digit = frac_s[i - int_chars] - '0';
    else
      digit = 0;
    if (digit == ('_' - '0'))
      continue;
    if (int_part > 0x19999999U || (int_part == 0x19999999U && digit > 5))
      cp_errno = CPE_OVERFLOW;
    int_part = int_part * 10 + digit;
  }
  
  // determine fraction part
  uint32_t frac_part = 0;
  digit = 0;
  
  for (int i = 0; i <= frac_digits; i++) {
    if (i + exponent + int_chars < 0)
      digit = 0;
    else if (i + exponent < 0)
      digit = int_s[i + exponent + int_chars] - '0';
    else if (i + exponent < frac_chars)
      digit = frac_s[i + exponent] - '0';
    else
      digit = 0;
    if (digit == ('_' - '0'))
      continue;
    // skip out on the last digit
    if (i == frac_digits)
      break;
    // no overflow possible b/c frac_digits was limited
    frac_part = frac_part * 10 + digit;
  }

  // round fraction part if required
  if (digit >= 5) {
    if (frac_part == exp10val[frac_digits] - 1) {
      frac_part = 0;
      if (int_part == 0xFFFFFFFFU)
	cp_errno = CPE_OVERFLOW;
      int_part++;
    } else
      frac_part++;
  }
  
  // done!
  if (cp_errno) {		// overflow
    int_part = 0xFFFFFFFFU;
    frac_part = exp10val[frac_digits] - 1;
  }

  //click_chatter("%d: %u %u", frac_digits, int_part, frac_part);
  *return_int_part = int_part;
  *return_frac_part = frac_part;
  return true;
}

static bool
unsigned_real10_2to1(uint32_t int_part, uint32_t frac_part, int frac_digits,
		     uint32_t *return_value)
{
  uint32_t one = exp10val[frac_digits];
  uint32_t int_max = 0xFFFFFFFFU / one;
  uint32_t frac_max = 0xFFFFFFFFU - int_max * one;
  if (int_part > int_max || (int_part == int_max && frac_part > frac_max)) {
    cp_errno = CPE_OVERFLOW;
    *return_value = 0xFFFFFFFFU;
  } else
    *return_value = int_part * one + frac_part;
  return true;
}

bool
cp_unsigned_real10(const String &str, int frac_digits,
		   uint32_t *return_int_part, uint32_t *return_frac_part)
{
  return cp_unsigned_real10(str, frac_digits, 0,
			    return_int_part, return_frac_part);
}

bool
cp_unsigned_real10(const String &str, int frac_digits, int exponent_delta,
		   uint32_t *return_value)
{
  uint32_t int_part, frac_part;
  if (!cp_unsigned_real10(str, frac_digits, exponent_delta, &int_part, &frac_part))
    return false;
  else
    return unsigned_real10_2to1(int_part, frac_part, frac_digits, return_value);
}

bool
cp_unsigned_real10(const String &str, int frac_digits,
		   uint32_t *return_value)
{
  uint32_t int_part, frac_part;
  if (!cp_unsigned_real10(str, frac_digits, 0, &int_part, &frac_part))
    return false;
  else
    return unsigned_real10_2to1(int_part, frac_part, frac_digits, return_value);
}

static uint32_t ureal2_digit_fractions[] = {
  0x00000000, 0x19999999, 0x33333333, 0x4CCCCCCC, 0x66666666,
  0x80000000, 0x99999999, 0xB3333333, 0xCCCCCCCC, 0xE6666666
};

bool
cp_unsigned_real2(const String &str, int frac_bits, uint32_t *return_value)
{
  if (frac_bits < 0 || frac_bits > CP_REAL2_MAX_FRAC_BITS) {
    cp_errno = CPE_INVALID;
    return false;
  }
  
  uint32_t int_part, frac_part;
  if (!cp_unsigned_real10(str, 9, 0, &int_part, &frac_part)) {
    cp_errno = CPE_FORMAT;
    return false;
  }

  // method from Knuth's TeX, round_decimals. Works well with
  // cp_unparse_real2 below
  uint32_t fraction = 0;
  for (int i = 0; i < 9; i++) {
    uint32_t digit = frac_part % 10;
    fraction = (fraction / 10) + ureal2_digit_fractions[digit];
    frac_part /= 10;
  }
  fraction = ((fraction >> (31 - frac_bits)) + 1) / 2;

  // This can happen! (for example, 16 bits of fraction, .999999) Why?
  if (fraction == (1U << frac_bits) && int_part < 0xFFFFFFFFU)
    int_part++, fraction = 0;

  // check for overflow
  if (cp_errno || int_part > (1U << (32 - frac_bits)) - 1) {
    cp_errno = CPE_OVERFLOW;
    *return_value = 0xFFFFFFFFU;
  } else
    *return_value = (int_part << frac_bits) + fraction;
  
  return true;
}


// Parsing signed reals

static bool
cp_real_base(const String &in_str, int frac_digits, int32_t *return_value,
	     bool (*func)(const String &, int, uint32_t *))
{
  String str = in_str;
  bool negative = false;
  if (str.length() > 1 && str[0] == '-' && str[1] != '+') {
    negative = true;
    str = str.substring(1);
  }

  uint32_t value;
  if (!func(str, frac_digits, &value))
    return false;

  // check for overflow
  uint32_t umax = (negative ? 0x80000000 : 0x7FFFFFFF);
  if (value > umax) {
    cp_errno = CPE_OVERFLOW;
    value = umax;
  }

  *return_value = (negative ? -value : value);
  return true;
}

bool
cp_real10(const String &str, int frac_digits, int32_t *return_value)
{
  return cp_real_base(str, frac_digits, return_value, cp_unsigned_real10);
}

bool
cp_real2(const String &str, int frac_bits, int32_t *return_value)
{
  return cp_real_base(str, frac_bits, return_value, cp_unsigned_real2);
}

#ifdef HAVE_FLOAT_TYPES
bool
cp_double(const String &in_str, double *result)
{
  cp_errno = CPE_FORMAT;
  if (in_str.length() == 0 || isspace((unsigned char) in_str[0]))
    // check for space because strtod() accepts leading whitespace
    return false;

  errno = 0;
  String str = in_str;
  char *endptr;
  double val = strtod(str.c_str(), &endptr);
  if (*endptr)			// bad format; garbage after number
    return false;

  cp_errno = (errno == ERANGE ? CPE_OVERFLOW : 0);
  *result = val;
  return true;
}
#endif

// PARSING TIME

static const char *
read_unit(const char *s, const char *end, 
	  const char *unit_begin_in, int unit_len, const char *prefix,
	  int *power, int *factor)
{
  const char *work = end;
  const unsigned char *unit_begin = reinterpret_cast<const unsigned char *>(unit_begin_in);
  const unsigned char *unit = unit_begin + unit_len;
  if (unit > unit_begin && unit[-1] == 0)
    unit--;
  while (unit > unit_begin) {
    if (unit[-1] < 4) {
	int type = unit[-1];
	assert(unit - 3 - (type >= 2) >= unit_begin);
	*factor = unit[-2];
	if (type >= 2)
	    unit--, *factor += 256 * unit[-2];
	*power = (type & 1 ? -(int) unit[-3] : unit[-3]);

	// check for SI prefix
	if (prefix && work > s) {
	    for (; *prefix; prefix += 2)
		if (*prefix == work[-1]) {
		    *power += (int) prefix[1] - 64;
		    work--;
		    break;
		}
	}
      
	while (work > s && isspace((unsigned char) work[-1]))
	    work--;
	return work;
    } else if (unit[-1] != (unsigned char) work[-1]) {
      while (unit > unit_begin && unit[-1] >= 4)
	unit--;
      unit -= 3 + (unit[-1] >= 2);
      work = end;
    } else {
      unit--;
      work--;
    }
  }
  return end;
}

static const char seconds_units[] = "\
\0\1\0s\
\0\1\0sec\
\1\6\0m\
\1\6\0min\
\2\044\0h\
\2\044\0hr\
\2\003\140\2d\
\2\003\140\2day";
static const char seconds_prefixes[] = "m\075u\072n\067";

bool cp_seconds_as(int want_power, const String &str, uint32_t *return_value)
{
  int power = 0, factor = 1;
  const char *after_unit = read_unit(str.begin(), str.end(), seconds_units, sizeof(seconds_units), seconds_prefixes, &power, &factor);
  if (!cp_unsigned_real10(str.substring(str.begin(), after_unit), want_power, power, return_value))
    return false;
  if (*return_value > 0xFFFFFFFFU / factor) {
    cp_errno = CPE_OVERFLOW;
    *return_value = 0xFFFFFFFFU;
  } else
    *return_value *= factor;
  return true;
}

bool cp_seconds_as_milli(const String &str_in, uint32_t *return_value)
{
  return cp_seconds_as(3, str_in, return_value);
}

bool cp_seconds_as_micro(const String &str_in, uint32_t *return_value)
{
  return cp_seconds_as(6, str_in, return_value);
}

bool cp_time(const String &str, Timestamp* return_value)
{
    int power = 0, factor = 1;
    const char *after_unit = read_unit(str.begin(), str.end(), seconds_units, sizeof(seconds_units), seconds_prefixes, &power, &factor);
    uint32_t sec, nsec;
    if (!cp_unsigned_real10(str.substring(str.begin(), after_unit), 9, power, &sec, &nsec))
	return false;
    if (factor != 1) {
	nsec *= factor;
	int delta = nsec / 1000000000;
	nsec -= delta * 1000000000;
	sec = (sec * factor) + delta;
    }
    *return_value = Timestamp::make_nsec(sec, nsec);
    return true;
}

bool cp_time(const String &str, timeval *return_value)
{
    return cp_time(str, (Timestamp*) return_value);
}


static const char byte_bandwidth_units[] = "\
\3\175\1baud\
\3\175\1bps\
\3\175\1b/s\
\0\1\0Bps\
\0\1\0B/s\
";
static const char byte_bandwidth_prefixes[] = "k\103K\103M\106G\111";

bool
cp_bandwidth(const String &str, uint32_t *return_value)
{
  int power = 0, factor = 1;
  const char *after_unit = read_unit(str.begin(), str.end(), byte_bandwidth_units, sizeof(byte_bandwidth_units), byte_bandwidth_prefixes, &power, &factor);
  if (!cp_unsigned_real10(str.substring(str.begin(), after_unit), 0, power, return_value))
    return false;
  if (*return_value > 0xFFFFFFFFU / factor) {
    cp_errno = CPE_OVERFLOW;
    *return_value = 0xFFFFFFFFU;
  } else {
    if (after_unit == str.end())
      cp_errno = CPE_NOUNITS;
    *return_value *= factor;
  }
  return true;
}



// PARSING IPv4 ADDRESSES

static int
ip_address_portion(const String &str, unsigned char *value)
{
  const unsigned char *s = reinterpret_cast<const unsigned char*>(str.data());
  int len = str.length();
  int pos = 0, part;

  for (int d = 0; d < 4; d++) {
    if (d && pos < len && s[pos] == '.')
      pos++;
    if (pos >= len) {
      memset(value + d, 0, 4 - d);
      return d;
    } else if (!isdigit(s[pos]))
      return 0;
    for (part = 0; pos < len && isdigit(s[pos]) && part <= 255; pos++)
      part = part*10 + s[pos] - '0';
    if (part > 255)
      return 0;
    value[d] = part;
  }

  return (pos == len ? 4 : 0);
}

bool
cp_ip_address(const String &str, unsigned char *return_value
	      CP_CONTEXT_ARG)
{
  unsigned char value[4];
  if (ip_address_portion(str, value) == 4) {
    memcpy(return_value, value, 4);
    return true;
  }
#ifndef CLICK_TOOL
  return AddressInfo::query_ip(str, return_value, context);
#else
  return false;
#endif
}


static bool
bad_ip_prefix(const String &str,
	      unsigned char *return_value, unsigned char *return_mask,
	      bool allow_bare_address
	      CP_CONTEXT_ARG)
{
#ifndef CLICK_TOOL
  if (AddressInfo::query_ip_prefix(str, return_value, return_mask, context))
    return true;
  else if (allow_bare_address
	   && AddressInfo::query_ip(str, return_value, context)) {
    return_mask[0] = return_mask[1] = return_mask[2] = return_mask[3] = 255;
    return true;
  }
#else
  // shut up, compiler!
  (void)str, (void)return_value, (void)return_mask, (void)allow_bare_address;
#endif
  return false;
}

bool
cp_ip_prefix(const String &str,
	     unsigned char *return_value, unsigned char *return_mask,
	     bool allow_bare_address  CP_CONTEXT_ARG)
{
  do {
    unsigned char value[4], mask[4];

    int slash = str.find_right('/');
    String ip_part, mask_part;
    if (slash < 0 && allow_bare_address)
      ip_part = str;
    else if (slash >= 0 && slash < str.length() - 1) {
      ip_part = str.substring(0, slash);
      mask_part = str.substring(slash + 1);
    } else
      goto failure;

    // read IP address part
    int good_ip_bytes = ip_address_portion(ip_part, value);
    if (good_ip_bytes == 0) {
      if (!cp_ip_address(ip_part, value  CP_PASS_CONTEXT))
	goto failure;
      good_ip_bytes = 4;
    }

    // check mask
    if (allow_bare_address && !mask_part.length() && good_ip_bytes == 4) {
      memcpy(return_value, value, 4);
      return_mask[0] = return_mask[1] = return_mask[2] = return_mask[3] = 255;
      return true;
    }

    // check for complete IP address
    int relevant_bits;
    if (good_ip_bytes == 4 && cp_ip_address(mask_part, mask  CP_PASS_CONTEXT))
      /* OK */;
  
    else if (cp_integer(mask_part, &relevant_bits)
	     && relevant_bits >= 0 && relevant_bits <= 32) {
      // set bits
      unsigned umask = 0;
      if (relevant_bits > 0)
	umask = 0xFFFFFFFFU << (32 - relevant_bits);
      for (int i = 0; i < 4; i++, umask <<= 8)
	mask[i] = (umask >> 24) & 255;
      if (good_ip_bytes < (relevant_bits + 7)/8)
	goto failure;
    
    } else
      goto failure;

    memcpy(return_value, value, 4);
    memcpy(return_mask, mask, 4);
    return true;
    
  } while (0);

 failure:
  return bad_ip_prefix(str, return_value, return_mask, allow_bare_address CP_PASS_CONTEXT);
}

bool
cp_ip_address(const String &str, IPAddress *address
	      CP_CONTEXT_ARG)
{
  return cp_ip_address(str, address->data()
		       CP_PASS_CONTEXT);
}

bool
cp_ip_prefix(const String &str, IPAddress *address, IPAddress *mask,
	     bool allow_bare_address  CP_CONTEXT_ARG)
{
  return cp_ip_prefix(str, address->data(), mask->data(),
		      allow_bare_address  CP_PASS_CONTEXT);
}

bool
cp_ip_prefix(const String &str, unsigned char *address, unsigned char *mask
	     CP_CONTEXT_ARG)
{
  return cp_ip_prefix(str, address, mask,
		      false  CP_PASS_CONTEXT);
}

bool
cp_ip_prefix(const String &str, IPAddress *address, IPAddress *mask
	     CP_CONTEXT_ARG)
{
  return cp_ip_prefix(str, address->data(), mask->data(),
		      false  CP_PASS_CONTEXT);
}

bool
cp_ip_address_list(const String &str, IPAddressList *l
		   CP_CONTEXT_ARG)
{
  Vector<String> words;
  cp_spacevec(str, words);
  StringAccum sa;
  IPAddress ip;
  for (int i = 0; i < words.size(); i++) {
    if (!cp_ip_address(words[i], &ip  CP_PASS_CONTEXT))
      return false;
    if (char *x = sa.extend(4))
      *reinterpret_cast<uint32_t *>(x) = ip.addr();
    else {
      cp_errno = CPE_MEMORY;
      return false;
    }
  }
  l->assign(words.size(), reinterpret_cast<uint32_t *>(sa.take_bytes()));
  return true;
}


// PARSING IPv6 ADDRESSES

#ifdef HAVE_IP6

static bool
bad_ip6_address(const String &str, unsigned char *return_value
		CP_CONTEXT_ARG)
{
#ifndef CLICK_TOOL
  return AddressInfo::query_ip6(str, return_value, context);
#else
  (void)str, (void)return_value;
  return false;
#endif
}

bool
cp_ip6_address(const String &str, unsigned char *return_value
	       CP_CONTEXT_ARG)
{
  unsigned short parts[8];
  int coloncolon = -1;
  const char *s = str.data();
  int len = str.length();
  int pos = 0;

  int d;
  int last_part_pos = 0;
  for (d = 0; d < 8; d++) {
    if (coloncolon < 0 && pos < len - 1 && s[pos] == ':' && s[pos+1] == ':') {
      coloncolon = d;
      pos += 2;
    } else if (d && pos < len - 1 && s[pos] == ':' && isxdigit((unsigned char) s[pos+1]))
      pos++;
    if (pos >= len || !isxdigit((unsigned char) s[pos]))
      break;
    unsigned part = 0;
    last_part_pos = pos;
    for (; pos < len && isxdigit((unsigned char) s[pos]) && part <= 0xFFFF; pos++)
      part = (part<<4) + xvalue((unsigned char) s[pos]);
    if (part > 0xFFFF)
      return bad_ip6_address(str, return_value  CP_PASS_CONTEXT);
    parts[d] = part;
  }

  // check if address ends in IPv4 address
  if (pos < len && d <= 7 && s[pos] == '.') {
    unsigned char ip4a[4];
    if (cp_ip_address(str.substring(last_part_pos), ip4a  CP_PASS_CONTEXT)) {
      parts[d-1] = (ip4a[0]<<8) + ip4a[1];
      parts[d] = (ip4a[2]<<8) + ip4a[3];
      d++;
      pos = len;
    }
  }

  // handle zero blocks surrounding ::
  if ((d < 8 && coloncolon < 0) || (d == 8 && coloncolon >= 0))
    return bad_ip6_address(str, return_value  CP_PASS_CONTEXT);
  else if (d < 8) {
    int num_zeros = 8 - d;
    for (int x = d - 1; x >= coloncolon; x--)
      parts[x + num_zeros] = parts[x];
    for (int x = coloncolon; x < coloncolon + num_zeros; x++)
      parts[x] = 0;
  }

  // return
  if (pos < len)
    return bad_ip6_address(str, return_value  CP_PASS_CONTEXT);
  else {
    for (d = 0; d < 8; d++) {
      return_value[d<<1] = (parts[d]>>8) & 0xFF;
      return_value[(d<<1) + 1] = parts[d] & 0xFF;
    }
    return true;
  }
}

bool
cp_ip6_address(const String &str, IP6Address *address
	       CP_CONTEXT_ARG)
{
  return cp_ip6_address(str, address->data()  CP_PASS_CONTEXT);
}


static bool
bad_ip6_prefix(const String &str,
	       unsigned char *return_value, int *return_bits,
	       bool allow_bare_address
	       CP_CONTEXT_ARG)
{
#ifndef CLICK_TOOL
  if (AddressInfo::query_ip6_prefix(str, return_value, return_bits, context))
    return true;
  else if (allow_bare_address
	   && AddressInfo::query_ip6(str, return_value, context)) {
    *return_bits = 128;
    return true;
  }
#else
  // shut up, compiler!
  (void)str, (void)return_value, (void)return_bits, (void)allow_bare_address;
#endif
  return false;
}

bool
cp_ip6_prefix(const String &str,
	      unsigned char *return_value, int *return_bits,
	      bool allow_bare_address  CP_CONTEXT_ARG)
{
  unsigned char value[16], mask[16];

  int slash = str.find_right('/');
  String ip_part, mask_part;
  if (slash >= 0) {
    ip_part = str.substring(0, slash);
    mask_part = str.substring(slash + 1);
  } else if (!allow_bare_address)
    return bad_ip6_prefix(str, return_value, return_bits, allow_bare_address CP_PASS_CONTEXT);
  else
    ip_part = str;
  
  if (!cp_ip6_address(ip_part, value  CP_PASS_CONTEXT))
    return bad_ip6_prefix(str, return_value, return_bits, allow_bare_address CP_PASS_CONTEXT);

  // move past /
  if (allow_bare_address && !mask_part.length()) {
    memcpy(return_value, value, 16);
    *return_bits = 64;
    return true;
  }

  // check for complete IP address
  int relevant_bits = 0;
  if (cp_ip6_address(mask_part, mask  CP_PASS_CONTEXT)) {
    // check that it really is a prefix. if not, return false right away
    // (don't check with AddressInfo)
    relevant_bits = IP6Address(mask).mask_to_prefix_len();
    if (relevant_bits < 0)
      return false;
    
  } else if (cp_integer(mask_part, &relevant_bits)
	     && relevant_bits >= 0 && relevant_bits <= 128)
    /* OK */;
    
  else
    return bad_ip6_prefix(str, return_value, return_bits, allow_bare_address CP_PASS_CONTEXT);

  memcpy(return_value, value, 16);
  *return_bits = relevant_bits;
  return true;
}

bool
cp_ip6_prefix(const String &str, unsigned char *address, unsigned char *mask,
	      bool allow_bare_address  CP_CONTEXT_ARG)
{
  int bits;
  if (cp_ip6_prefix(str, address, &bits, allow_bare_address  CP_PASS_CONTEXT)) {
    IP6Address m = IP6Address::make_prefix(bits);
    memcpy(mask, m.data(), 16);
    return true;
  } else
    return false;
}

bool
cp_ip6_prefix(const String &str, IP6Address *address, int *prefix,
	      bool allow_bare_address  CP_CONTEXT_ARG)
{
  return cp_ip6_prefix(str, address->data(), prefix, allow_bare_address  CP_PASS_CONTEXT);
}

bool
cp_ip6_prefix(const String &str, IP6Address *address, IP6Address *prefix,
	      bool allow_bare_address  CP_CONTEXT_ARG)
{
  return cp_ip6_prefix(str, address->data(), prefix->data(), allow_bare_address  CP_PASS_CONTEXT);
}

#endif /* HAVE_IP6 */


bool
cp_ethernet_address(const String &str, unsigned char *return_value
		    CP_CONTEXT_ARG)
{
  int i = 0;
  const unsigned char* s = reinterpret_cast<const unsigned char*>(str.data());
  int len = str.length();

  unsigned char value[6];
  for (int d = 0; d < 6; d++) {
    if (i < len - 1 && isxdigit(s[i]) && isxdigit(s[i+1])) {
      value[d] = xvalue(s[i])*16 + xvalue(s[i+1]);
      i += 2;
    } else if (i < len && isxdigit(s[i])) {
      value[d] = xvalue(s[i]);
      i += 1;
    } else
      goto bad;
    if (d == 5) break;
    if (i >= len - 1 || s[i] != ':')
      goto bad;
    i++;
  }

  if (i == len) {
    memcpy(return_value, value, 6);
    return true;
  }

 bad:
#ifndef CLICK_TOOL
  return AddressInfo::query_ethernet(str, return_value, context);
#else
  return false;
#endif
}

bool
cp_ethernet_address(const String &str, EtherAddress *address
		    CP_CONTEXT_ARG)
{
  return cp_ethernet_address(str, address->data()
			     CP_PASS_CONTEXT);
}


bool
cp_tcpudp_port(const String &str, int ip_p, uint16_t *return_value
	       CP_CONTEXT_ARG)
{
    uint32_t value;
    assert(ip_p > 0 && ip_p < 256);
#ifndef CLICK_TOOL
    if (!NameInfo::query_int(NameInfo::T_IP_PORT + ip_p, context, str, &value))
	return false;
#else
    (void) ip_p;
    if (!cp_unsigned(str, &value))
	return false;
#endif
    if (value <= 0xFFFF) {
	*return_value = value;
	return true;
    } else {
	cp_errno = CPE_OVERFLOW;
	return false;
    }
}


#ifndef CLICK_TOOL
Element *
cp_element(const String &text_in, Element *context, ErrorHandler *errh)
{
  String name;
  if (!cp_string(text_in, &name)) {
    if (errh)
      errh->error("bad name format");
    return 0;
  } else
    return context->router()->find(name, context, errh);
}

Element *
cp_element(const String &text_in, Router *router, ErrorHandler *errh)
{
  String name;
  if (!cp_string(text_in, &name)) {
    if (errh)
      errh->error("bad name format");
    return 0;
  } else
    return router->find(name, errh);
}

bool
cp_handler_name(const String& str,
		Element** result_element, String* result_hname,
		Element* context, ErrorHandler* errh)
{
  if (!errh)
    errh = ErrorHandler::silent_handler();
  
  String text;
  if (!cp_string(str, &text) || !text) {
    errh->error("bad handler name format");
    return false;
  }

  const char *leftmost_dot = find(text, '.');
  if (leftmost_dot == text.end() || leftmost_dot == text.begin()) {
    *result_element = context->router()->root_element();
    *result_hname = (leftmost_dot == text.begin() ? text.substring(text.begin() + 1, text.end()) : text);
    return true;
  } else if (leftmost_dot == text.end() - 1) {
    errh->error("empty handler name");
    return false;
  }

  Element *e = context->router()->find(text.substring(text.begin(), leftmost_dot), context, errh);
  if (!e)
    return false;

  *result_element = e;
  *result_hname = text.substring(leftmost_dot + 1, text.end());
  return true;
}

bool
cp_handler(const String &str, int flags,
	   Element** result_element, const Handler** result_h,
	   Element* context, ErrorHandler* errh)
{
  HandlerCall hc(str);
  if (hc.initialize(flags, context, errh) < 0)
    return false;
  else {
    *result_element = hc.element();
    *result_h = hc.handler();
    return true;
  }
}
#endif

#ifdef HAVE_IPSEC
bool
cp_des_cblock(const String &str, unsigned char *return_value)
{
  int i = 0;
  const unsigned char *s = reinterpret_cast<const unsigned char*>(str.data());
  int len = str.length();
  
  if (len != 16)
    return false;

  unsigned char value[8];
  for (int d = 0; d < 8; d++) {
    if (i < len - 1 && isxdigit(s[i]) && isxdigit(s[i+1])) {
      value[d] = xvalue(s[i])*16 + xvalue(s[i+1]);
      i += 2;
    } else
      return false;
  }

  if (len != i)
    return false;
  else {
    memcpy(return_value, value, 8);
    return true;
  }
}
#endif

#ifdef CLICK_USERLEVEL
bool
cp_filename(const String &str, String *return_value)
{
  String fn;
  if (!cp_string(str, &fn) || !fn)
    return false;

  // expand home directory substitutions
  if (fn[0] == '~') {
    if (fn.length() == 1 || fn[1] == '/') {
      const char *home = getenv("HOME");
      if (home)
	fn = String(home) + fn.substring(1);
    } else {
      int off = 1;
      while (off < fn.length() && fn[off] != '/')
	off++;
      String username = fn.substring(1, off - 1);
      struct passwd *pwd = getpwnam(username.c_str());
      if (pwd && pwd->pw_dir)
	fn = String(pwd->pw_dir) + fn.substring(off);
    }
  }

  // replace double slashes with single slashes
  int len = fn.length();
  for (int i = 0; i < len - 1; i++)
    if (fn[i] == '/' && fn[i+1] == '/') {
      fn = fn.substring(0, i) + fn.substring(i + 1);
      i--;
      len--;
    }

  // return
  *return_value = fn;
  return true;
}
#endif


//
// CP_VA_PARSE AND FRIENDS
//

// parse commands; those which must be recognized inside a keyword section
// must begin with "\377"

const CpVaParseCmd
  cpEnd			= 0,
  cpOptional		= "OPTIONAL",
  cpKeywords		= "\377KEYWORDS",
  cpConfirmKeywords	= "\377CONFIRM_KEYWORDS",
  cpMandatoryKeywords	= "\377MANDATORY_KEYWORDS",
  cpIgnore		= "IGNORE",
  cpIgnoreRest		= "\377IGNORE_REST",
  cpArgument		= "arg",
  cpArguments		= "args",
  cpString		= "string",
  cpWord		= "word",
  cpKeyword		= "keyword",
  cpBool		= "bool",
  cpByte		= "byte",
  cpShort		= "short",
  cpUnsignedShort	= "u_short",
  cpInteger		= "int",
  cpUnsigned		= "u_int",
  cpNamedInteger	= "named_int",
  cpInteger64		= "long_long",
  cpUnsigned64		= "u_long_long",
  cpFileOffset		= "off_t",
  cpReal2		= "real2",
  cpUnsignedReal2	= "u_real2",
  cpReal10		= "real10",
  cpUnsignedReal10	= "u_real10",
  cpDouble		= "double",
  cpSeconds		= "sec",
  cpSecondsAsMilli	= "msec",
  cpSecondsAsMicro	= "usec",
  cpTimeval		= "timeval",
  cpTimestamp		= "timestamp",
  cpInterval		= "interval",
  cpBandwidth		= "bandwidth_Bps",
  cpIPAddress		= "ip_addr",
  cpIPPrefix		= "ip_prefix",
  cpIPAddressOrPrefix	= "ip_addr_or_prefix",
  cpIPAddressList	= "ip_addr_list",
  cpEthernetAddress	= "ether_addr",
  cpEtherAddress	= "ether_addr", // synonym
  cpTCPPort		= "tcp_port",
  cpUDPPort		= "udp_port",
  cpElement		= "element",
  cpHandlerName		= "handler_name",
  cpHandler		= "handler",
  cpReadHandlerCall	= "read_handler_call",
  cpWriteHandlerCall	= "write_handler_call",
  cpIP6Address		= "ip6_addr",
  cpIP6Prefix		= "ip6_prefix",
  cpIP6AddressOrPrefix	= "ip6_addr_or_prefix",
  cpDesCblock		= "des_cblock",
  cpFilename		= "filename";

enum {
  cpiEnd = 0,
  cpiOptional,
  cpiKeywords,
  cpiConfirmKeywords,
  cpiMandatoryKeywords,
  cpiIgnore,
  cpiIgnoreRest,
  cpiArgument,
  cpiArguments,
  cpiString,
  cpiWord,
  cpiKeyword,
  cpiBool,
  cpiByte,
  cpiShort,
  cpiUnsignedShort,
  cpiInteger,
  cpiUnsigned,
  cpiNamedInteger,
  cpiInteger64,
  cpiUnsigned64,
  cpiFileOffset,
  cpiReal2,
  cpiUnsignedReal2,
  cpiReal10,
  cpiUnsignedReal10,
  cpiDouble,
  cpiSeconds,
  cpiSecondsAsMilli,
  cpiSecondsAsMicro,
  cpiTimeval,
  cpiInterval,
  cpiBandwidth,
  cpiIPAddress,
  cpiIPPrefix,
  cpiIPAddressOrPrefix,
  cpiIPAddressList,
  cpiEthernetAddress,
  cpiTCPPort,
  cpiUDPPort,
  cpiElement,
  cpiHandlerName,
  cpiReadHandlerCall,
  cpiWriteHandlerCall,
  cpiIP6Address,
  cpiIP6Prefix,
  cpiIP6AddressOrPrefix,
  cpiDesCblock,
  cpiFilename
};

#define NARGTYPE_HASH 128
static cp_argtype *argtype_hash[NARGTYPE_HASH];

static inline int
argtype_bucket(const char *command)
{
  const unsigned char *s = (const unsigned char *)command;
  return (s[0] ? (s[0]%32 + strlen(command)*32) % NARGTYPE_HASH : 0);
}

static const cp_argtype *
cp_find_argtype(const char *command)
{
  cp_argtype *t = argtype_hash[argtype_bucket(command)];
  while (t && strcmp(t->name, command) != 0)
    t = t->next;
  return t;
}

static int
cp_register_argtype(const char *name, const char *desc, int flags,
		    cp_parsefunc parse, cp_storefunc store, int internal,
		    void *user_data = 0)
{
    if (cp_argtype *t = const_cast<cp_argtype *>(cp_find_argtype(name))) {
	t->use_count++;
	if (strcmp(desc, t->description) != 0
	    || flags != t->flags
	    || parse != t->parse
	    || store != t->store
	    || internal != t->internal)
	    return -EEXIST;
	else
	    return t->use_count - 1;
    }
  
    if (cp_argtype *t = new cp_argtype) {
	t->name = name;
	t->parse = parse;
	t->store = store;
	t->user_data = user_data;
	t->flags = flags;
	t->description = desc;
	t->internal = internal;
	t->use_count = 1;
	int bucket = argtype_bucket(name);
	t->next = argtype_hash[bucket];
	argtype_hash[bucket] = t;
	return 0;
    } else
	return -ENOMEM;
}

int
cp_register_argtype(const char *name, const char *desc, int flags,
		    cp_parsefunc parse, cp_storefunc store, void *user_data)
{
    return cp_register_argtype(name, desc, flags, parse, store, -1, user_data);
}


static void
default_parsefunc(cp_value *v, const String &arg,
		  ErrorHandler *errh, const char *argname  CP_CONTEXT_ARG)
{
  const char *desc = v->description;
  int underflower = -0x80000000;
  unsigned overflower = 0xFFFFFFFFU;
  const cp_argtype *argtype = v->argtype;
  
  switch (argtype->internal) {
    
   case cpiArgument:
   case cpiArguments:
    // nothing to do
    break;
    
   case cpiString:
    if (!cp_string(arg, &v->v_string))
      errh->error("%s takes string (%s)", argname, desc);
    break;
    
   case cpiWord:
    if (!cp_word(arg, &v->v_string))
      errh->error("%s takes word (%s)", argname, desc);
    break;
    
   case cpiKeyword:
    if (!cp_keyword(arg, &v->v_string))
      errh->error("%s takes keyword (%s)", argname, desc);
    break;
    
   case cpiBool:
    if (!cp_bool(arg, &v->v.b))
      errh->error("%s takes bool (%s)", argname, desc);
    break;

   case cpiByte:
    overflower = 255;
    goto handle_unsigned;

   case cpiShort:
    underflower = -0x8000;
    overflower = 0x7FFF;
    goto handle_signed;

   case cpiUnsignedShort:
    overflower = 0xFFFF;
    goto handle_unsigned;
    
   case cpiInteger:
   handle_int32_t:
    underflower = -0x80000000;
    overflower = 0x7FFFFFFF;
    goto handle_signed;

   case cpiUnsigned:
    overflower = 0xFFFFFFFFU;
    goto handle_unsigned;

    case cpiNamedInteger:
#ifndef CLICK_TOOL
      if (NameInfo::query(v->extra, context, arg, &v->v.i, 4))
	  break;
#endif
      goto handle_int32_t;

   handle_signed:
    if (!cp_integer(arg, &v->v.i))
      errh->error("%s takes %s (%s)", argname, argtype->description, desc);
    else if (cp_errno == CPE_OVERFLOW)
      errh->error("%s (%s) too large; max %d", argname, desc, v->v.i);
    else if (v->v.i < underflower)
      errh->error("%s (%s) must be >= %d", argname, desc, underflower);
    else if (v->v.i > (int)overflower)
      errh->error("%s (%s) must be <= %u", argname, desc, overflower);
    break;

   handle_unsigned:
    if (!cp_unsigned(arg, &v->v.u))
      errh->error("%s takes %s (%s)", argname, argtype->description, desc);
    else if (cp_errno == CPE_OVERFLOW)
      errh->error("%s (%s) too large; max %u", argname, desc, v->v.u);
    else if (v->v.u > overflower)
      errh->error("%s (%s) must be <= %u", argname, desc, overflower);
    break;

#ifdef HAVE_INT64_TYPES
   case cpiInteger64:
    if (!cp_integer(arg, &v->v.i64))
      errh->error("%s takes %s (%s)", argname, argtype->description, desc);
    else if (cp_errno == CPE_OVERFLOW)
      errh->error("%s (%s) too large; max %^64d", argname, desc, v->v.i64);
    break;

   case cpiUnsigned64:
    if (!cp_unsigned(arg, &v->v.u64))
      errh->error("%s takes %s (%s)", argname, argtype->description, desc);
    else if (cp_errno == CPE_OVERFLOW)
      errh->error("%s (%s) too large; max %^64u", argname, desc, v->v.u64);
    break;
#endif

#ifdef CLICK_USERLEVEL
   case cpiFileOffset:
    if (!cp_file_offset(arg, (off_t *) &v->v))
      errh->error("%s takes %s (%s)", argname, argtype->description, desc);
    break;
#endif

   case cpiReal10:
    if (!cp_real10(arg, v->extra, &v->v.i))
      errh->error("%s takes real (%s)", argname, desc);
    else if (cp_errno == CPE_OVERFLOW) {
      String m = cp_unparse_real10(v->v.i, v->extra);
      errh->error("%s (%s) too large; max %s", argname, desc, m.c_str());
    }
    break;

   case cpiUnsignedReal10:
    if (!cp_unsigned_real10(arg, v->extra, &v->v.u))
      errh->error("%s takes unsigned real (%s)", argname, desc);
    else if (cp_errno == CPE_OVERFLOW) {
      String m = cp_unparse_real10(v->v.u, v->extra);
      errh->error("%s (%s) too large; max %s", argname, desc, m.c_str());
    }
    break;

#ifdef HAVE_FLOAT_TYPES
   case cpiDouble:
    if (!cp_double(arg, &v->v.d))
      errh->error("%s takes %s (%s)", argname, argtype->description, desc);
    else if (cp_errno == CPE_OVERFLOW)
      errh->error("%s (%s) out of range; limit %g", argname, desc, v->v.d);
    break;
#endif

   case cpiSeconds:
    if (!cp_seconds_as(0, arg, &v->v.u))
      errh->error("%s takes time in seconds (%s)", argname, desc);
    else if (cp_errno == CPE_OVERFLOW)
      errh->error("%s (%s) too large; max %u", argname, desc, v->v.u);
    break;

   case cpiSecondsAsMilli:
    if (!cp_seconds_as(3, arg, &v->v.u))
      errh->error("%s takes time in seconds (%s)", argname, desc);
    else if (cp_errno == CPE_OVERFLOW) {
      String m = cp_unparse_milliseconds(v->v.u);
      errh->error("%s (%s) too large; max %s", argname, desc, m.c_str());
    }
    break;

   case cpiSecondsAsMicro:
    if (!cp_seconds_as(6, arg, &v->v.u))
      errh->error("%s takes time in seconds (%s)", argname, desc);
    else if (cp_errno == CPE_OVERFLOW) {
      String m = cp_unparse_microseconds(v->v.u);
      errh->error("%s (%s) too large; max %s", argname, desc, m.c_str());
    }
    break;

   case cpiTimeval:
   case cpiInterval: {
     struct timeval tv;
     if (!cp_time(arg, &tv)) {
       if (cp_errno == CPE_NEGATIVE)
	 errh->error("%s (%s) must be >= 0", argname, desc);
       else
	 errh->error("%s takes %s (%s)", argname, argtype->description, desc);
     } else if (cp_errno == CPE_OVERFLOW)
       errh->error("%s (%s) too large", argname, desc);
     else {
       v->v.i = tv.tv_sec;
       v->v2.i = tv.tv_usec;
     }
     break;
   }

   case cpiBandwidth:
    if (!cp_bandwidth(arg, &v->v.u))
      errh->error("%s takes bandwidth (%s)", argname, desc);
    else if (cp_errno == CPE_OVERFLOW) {
      String m = cp_unparse_bandwidth(v->v.u);
      errh->error("%s (%s) too large; max %s", argname, desc, m.c_str());
    } else if (cp_errno == CPE_NOUNITS)
      errh->warning("no units on bandwidth %s (%s), assuming Bps", argname, desc);
    break;

   case cpiReal2:
    if (!cp_real2(arg, v->extra, &v->v.i)) {
      // CPE_INVALID would indicate a bad 'v->extra'
      errh->error("%s takes real (%s)", argname, desc);
    } else if (cp_errno == CPE_OVERFLOW) {
      String m = cp_unparse_real2(v->v.i, v->extra);
      errh->error("%s (%s) too large; max %s", argname, desc, m.c_str());
    }
    break;

   case cpiUnsignedReal2:
    if (!cp_unsigned_real2(arg, v->extra, &v->v.u)) {
      // CPE_INVALID would indicate a bad 'v->extra'
      errh->error("%s takes unsigned real (%s)", argname, desc);
    } else if (cp_errno == CPE_OVERFLOW) {
      String m  = cp_unparse_real2(v->v.u, v->extra);
      errh->error("%s (%s) too large; max %s", argname, desc, m.c_str());
    }
    break;

   case cpiIPAddress:
    if (!cp_ip_address(arg, v->v.address CP_PASS_CONTEXT))
      errh->error("%s takes IP address (%s)", argname, desc);
    break;

   case cpiIPPrefix:
   case cpiIPAddressOrPrefix: {
     bool mask_optional = (argtype->internal == cpiIPAddressOrPrefix);
     if (!cp_ip_prefix(arg, v->v.address, v->v2.address, mask_optional CP_PASS_CONTEXT))
       errh->error("%s takes IP address prefix (%s)", argname, desc);
     break;
   }

   case cpiIPAddressList: {
     IPAddressList l;
     if (!cp_ip_address_list(arg, &l CP_PASS_CONTEXT))
       errh->error("%s takes set of IP addresses (%s)", argname, desc);
     break;
   }

#ifdef HAVE_IP6
   case cpiIP6Address:
    if (!cp_ip6_address(arg, (unsigned char *)v->v.address))
      errh->error("%s takes IPv6 address (%s)", argname, desc);
    break;

   case cpiIP6Prefix:
   case cpiIP6AddressOrPrefix: {
     bool mask_optional = (argtype->internal == cpiIP6AddressOrPrefix);
     if (!cp_ip6_prefix(arg, v->v.address, v->v2.address, mask_optional CP_PASS_CONTEXT))
       errh->error("%s takes IPv6 address prefix (%s)", argname, desc);
     break;
   }
#endif

   case cpiEthernetAddress:
    if (!cp_ethernet_address(arg, v->v.address CP_PASS_CONTEXT))
      errh->error("%s takes Ethernet address (%s)", argname, desc);
    break;

   case cpiTCPPort:
    if (!cp_tcpudp_port(arg, IP_PROTO_TCP, (uint16_t *) v->v.address CP_PASS_CONTEXT))
      errh->error("%s takes TCP port (%s)", argname, desc);
    break;

   case cpiUDPPort:
    if (!cp_tcpudp_port(arg, IP_PROTO_UDP, (uint16_t *) v->v.address CP_PASS_CONTEXT))
      errh->error("%s takes UDP port (%s)", argname, desc);
    break;

#ifdef HAVE_IPSEC
   case cpiDesCblock:
    if (!cp_des_cblock(arg, v->v.address))
      errh->error("%s takes DES encryption block (%s)", argname, desc);
    break;
#endif

#ifndef CLICK_TOOL
   case cpiElement: {
     ContextErrorHandler cerrh(errh, String(argname) + " (" + desc + "):");
     v->v.element = cp_element(arg, context, &cerrh);
     break;
   }
   
   case cpiHandlerName: {
     ContextErrorHandler cerrh(errh, String(argname) + " (" + desc + "):");
     cp_handler_name(arg, &v->v.element, &v->v2_string, context, &cerrh);
     break;
   }

   case cpiReadHandlerCall:
    underflower = HandlerCall::CHECK_READ | HandlerCall::ALLOW_PREINITIALIZE;
    goto handler_call;

   case cpiWriteHandlerCall:
    underflower = HandlerCall::CHECK_WRITE | HandlerCall::ALLOW_PREINITIALIZE;
    goto handler_call;

   handler_call: {
     ContextErrorHandler cerrh(errh, String(argname) + " (" + desc + "):");
     HandlerCall garbage(arg);
     garbage.initialize(underflower, context, &cerrh);
     break;
   }
#endif

#ifdef CLICK_USERLEVEL
   case cpiFilename:
    if (!cp_filename(arg, &v->v_string))
      errh->error("%s takes filename (%s)", argname, desc);
    break;
#endif
    
  }
}

static void
default_storefunc(cp_value *v  CP_CONTEXT_ARG)
{
  int helper;
  const cp_argtype *argtype = v->argtype;
  
  if (v->store_confirm)
    *v->store_confirm = true;
  
  switch (argtype->internal) {
    
   case cpiBool: {
     bool *bstore = (bool *)v->store;
     *bstore = v->v.b;
     break;
   }
   
   case cpiByte: {
     uint8_t *ucstore = (uint8_t *)v->store;
     *ucstore = v->v.i;
     break;
   }

   case cpiShort: {
     short *sstore = (short *)v->store;
     *sstore = v->v.i;
     break;
   }

   case cpiUnsignedShort: {
     unsigned short *usstore = (unsigned short *)v->store;
     *usstore = v->v.u;
     break;
   }
    
   case cpiTCPPort:
   case cpiUDPPort: {
     uint16_t *u16store = (uint16_t *)v->store;
     *u16store = *((uint16_t *)v->v.address);
     break;
   }
   
   case cpiInteger:
   case cpiNamedInteger:
   case cpiReal2:
   case cpiReal10:
   case cpiSeconds:
   case cpiSecondsAsMilli:
   case cpiSecondsAsMicro:
   case cpiBandwidth: {
     int *istore = (int *)v->store;
     *istore = v->v.i;
     break;
   }
  
   case cpiUnsigned:
   case cpiUnsignedReal2:
   case cpiUnsignedReal10: {
     unsigned *ustore = (unsigned *)v->store; 
     *ustore = v->v.u; 
     break; 
   }

#ifdef HAVE_INT64_TYPES
   case cpiInteger64: {
     int64_t *llstore = (int64_t *)v->store;
     *llstore = v->v.i64;
     break;
   }

   case cpiUnsigned64: {
     uint64_t *ullstore = (uint64_t *)v->store;
     *ullstore = v->v.u64;
     break;
   }
#endif

#ifdef CLICK_USERLEVEL
   case cpiFileOffset: {
     off_t *offstore = (off_t *)v->store;
     *offstore = *((off_t *)&v->v);
     break;
   }
#endif

#ifdef HAVE_FLOAT_TYPES
   case cpiDouble: {
     double *dstore = (double *)v->store;
     *dstore = v->v.d;
     break;
   }
#endif

   case cpiTimeval:
   case cpiInterval: {
     struct timeval *tvstore = (struct timeval *)v->store;
     tvstore->tv_sec = v->v.i;
     tvstore->tv_usec = v->v2.i;
     break;
   }

   case cpiArgument:
   case cpiString:
   case cpiWord:
   case cpiKeyword:
   case cpiFilename: {
     String *sstore = (String *)v->store;
     *sstore = v->v_string;
     break;
   }

   case cpiArguments: {
     Vector<String> *vstore = (Vector<String> *)v->store;
     uint32_t pos = 0;
     const char *len_str = v->v2_string.data();
     for (int len_pos = 0; len_pos < v->v2_string.length(); len_pos += 4) {
       uint32_t pos2 = *((const uint32_t *)(len_str + len_pos));
       vstore->push_back(v->v_string.substring(pos, pos2 - pos));
       pos = pos2;
     }
     vstore->push_back(v->v_string.substring(pos));
     break;
   }

   case cpiIPAddress:
    helper = 4;
    goto address;

#ifdef HAVE_IP6
   case cpiIP6Address:
    helper = 16;
    goto address;
#endif

   case cpiEthernetAddress:
    helper = 6;
    goto address;

#ifdef HAVE_IPSEC
   case cpiDesCblock:
    helper = 8;
    goto address;
#endif
   
   address: {
     unsigned char *addrstore = (unsigned char *)v->store;
     memcpy(addrstore, v->v.address, helper);
     break;
   }

   case cpiIPPrefix:
   case cpiIPAddressOrPrefix: {
     unsigned char *addrstore = (unsigned char *)v->store;
     memcpy(addrstore, v->v.address, 4);
     unsigned char *maskstore = (unsigned char *)v->store2;
     memcpy(maskstore, v->v2.address, 4);
     break;
   }

#ifdef HAVE_IP6
   case cpiIP6Prefix:
   case cpiIP6AddressOrPrefix: {
     unsigned char *addrstore = (unsigned char *)v->store;
     memcpy(addrstore, v->v.address, 16);
     unsigned char *maskstore = (unsigned char *)v->store2;
     memcpy(maskstore, v->v2.address, 16);
     break;
   }
#endif

   case cpiIPAddressList: {
     // oog... parse set into stored set only when we know there are no errors
     IPAddressList *liststore = (IPAddressList *)v->store;
     cp_ip_address_list(v->v_string, liststore  CP_PASS_CONTEXT);
     break;
   }

#ifndef CLICK_TOOL
   case cpiElement: {
     Element **elementstore = (Element **)v->store;
     *elementstore = v->v.element;
     break;
   }

   case cpiHandlerName: {
     Element **elementstore = (Element **)v->store;
     String *hnamestore = (String *)v->store2;
     *elementstore = v->v.element;
     *hnamestore = v->v2_string;
     break;
   }

   case cpiReadHandlerCall:
    helper = HandlerCall::CHECK_READ | HandlerCall::ALLOW_PREINITIALIZE;
    goto handler_call;

   case cpiWriteHandlerCall:
    helper = HandlerCall::CHECK_WRITE | HandlerCall::ALLOW_PREINITIALIZE;
    goto handler_call;

   handler_call:
    HandlerCall::reset(*(HandlerCall**)v->store, v->v_string, helper, context, (ErrorHandler*)0);
    break;
#endif

   default:
    // no argument provided
    break;
    
  }
}


static void
stringlist_parsefunc(cp_value *v, const String &arg,
		     ErrorHandler *errh, const char *argname  CP_CONTEXT_ARG)
{
    const char *desc = v->description;
    const cp_argtype *argtype = v->argtype;
#ifndef CLICK_TOOL
    (void) context;
#endif

    if (HashMap<String, int> *m = reinterpret_cast<HashMap<String, int> *>(argtype->user_data)) {
	String word;
	if (cp_word(arg, &word))
	    if (int *valp = m->findp(word)) {
		v->v.i = *valp;
		return;
	    }
    }

    if (argtype->flags & cpArgAllowNumbers) {
	if (!cp_integer(arg, &v->v.i))
	    errh->error("%s takes %s (%s)", argname, argtype->description, desc);
	else if (cp_errno == CPE_OVERFLOW)
	    errh->error("%s (%s) too large; max %d", argname, desc, v->v.i);
    } else
	errh->error("%s takes %s (%s)", argname, argtype->description, desc);
}

int
cp_register_stringlist_argtype(const char *name, const char *desc, int flags)
{
    return cp_register_argtype(name, desc, flags, stringlist_parsefunc, default_storefunc, cpiInteger);
}

int
cp_extend_stringlist_argtype(const char *name, ...)
{
    cp_argtype *t = const_cast<cp_argtype *>(cp_find_argtype(name));
    if (!t || t->parse != stringlist_parsefunc)
	return -ENOENT;
    HashMap<String, int> *m = reinterpret_cast<HashMap<String, int> *>(t->user_data);
    if (!m)
	t->user_data = m = new HashMap<String, int>();
    if (!m)
	return -ENOMEM;
  
    va_list val;
    va_start(val, name);
    const char *s;
    int retval = 0;
    while ((s = va_arg(val, const char *))) {
	int value = va_arg(val, int);
	if (cp_is_word(s))
	    m->insert(String(s), value);
	else
	    retval = -1;
    }
    va_end(val);
    return retval;
}


void
cp_unregister_argtype(const char *name)
{
    cp_argtype **prev = &argtype_hash[argtype_bucket(name)];
    cp_argtype *trav = *prev;
    while (trav && strcmp(trav->name, name) != 0) {
	prev = &trav->next;
	trav = trav->next;
    }
    if (trav) {
	trav->use_count--;
	if (trav->use_count <= 0) {
	    if (trav->parse == stringlist_parsefunc)
		delete reinterpret_cast<HashMap<String, int> *>(trav->user_data);
	    *prev = trav->next;
	    delete trav;
	}
    }
}


#define CP_VALUES_SIZE 80
static cp_value *cp_values;
static Vector<int> *cp_parameter_used;

enum {
    kwSuccess = 0,
    kwDupKeyword = -1,
    kwNoKeyword = -2,
    kwUnkKeyword = -3,
    kwMissingKeyword = -4
};

static inline bool
special_argtype_for_keyword(const cp_argtype *t)
{
    return t && t->internal == cpiArguments;
}

static int
handle_special_argtype_for_keyword(cp_value *val, const String &rest)
{
  if (val->argtype->internal == cpiArguments) {
    if (val->v.i > 0) {
      uint32_t l = val->v_string.length();
      val->v2_string += String((const char *)&l, 4);
      val->v_string += rest;
    } else {
      val->v.i = 1;
      val->v_string = rest;
      val->v2_string = String();
    }
    return kwSuccess;
  } else {
    assert(0);
    return kwUnkKeyword;
  }
}


namespace {

struct CpVaHelper {

  CpVaHelper(struct cp_value *, int, bool keywords_only);

  int develop_values(va_list val, ErrorHandler *errh);

  int assign_keyword_argument(const String &);
  void add_keyword_error(StringAccum &, int err, const String &arg, const char *argname, int argno);
  int finish_keyword_error(const char *format, const char *bad_keywords, ErrorHandler *errh);

  int assign_arguments(const Vector<String> &args, const char *argname, ErrorHandler *errh);
  
  int parse_arguments(const char *argname  CP_CONTEXT_ARG, ErrorHandler *errh);

  bool keywords_only;
  int nvalues;
  int nrequired;
  int npositional;
  bool ignore_rest;

  struct cp_value *cp_values;
  int cp_values_size;
  
};

CpVaHelper::CpVaHelper(struct cp_value *cp_values_, int cp_values_size_,
		       bool keywords_only_)
  : keywords_only(keywords_only_), nvalues(0),
    nrequired(keywords_only ? 0 : -1), npositional(keywords_only ? 0 : -1),
    ignore_rest(keywords_only),
    cp_values(cp_values_), cp_values_size(cp_values_size_)
{
}

int
CpVaHelper::develop_values(va_list val, ErrorHandler *errh)
{
  if (!cp_values || !cp_parameter_used)
    return errh->error("out of memory in cp_va_parse");

  bool confirm_keywords = false;
  bool mandatory_keywords = false;

  while (1) {
    
    if (nvalues == cp_values_size - 1)
      // no more space to store information about the arguments; break
      return errh->error("too many arguments to cp_va_parse!");

    cp_value *v = &cp_values[nvalues];
    v->argtype = 0;
    v->keyword = 0;
    const char *command_name = 0;
    
    if (npositional >= 0) {
      // read keyword if necessary; be careful of special "cp" values,
      // which begin with "\377"
      v->keyword = va_arg(val, const char *);
      if (v->keyword == 0)
	goto done;
      else if (v->keyword[0] == '\377')
	command_name = v->keyword;
    }

    // read command name
    if (command_name == 0)
      command_name = va_arg(val, const char *);
    if (command_name == 0)
      goto done;

    // find cp_argtype
    const cp_argtype *argtype = cp_find_argtype(command_name);
    if (!argtype)
      return errh->error("unknown argument type '%s'!", command_name);
    
    // check for special commands
    if (argtype->internal == cpiOptional) {
      if (nrequired < 0)
	nrequired = nvalues;
      continue;
    } else if (argtype->internal == cpiKeywords
	       || argtype->internal == cpiConfirmKeywords
	       || argtype->internal == cpiMandatoryKeywords) {
      if (nrequired < 0)
	nrequired = nvalues;
      if (npositional < 0)
	npositional = nvalues;
      confirm_keywords = (argtype->internal == cpiConfirmKeywords);
      mandatory_keywords = (argtype->internal == cpiMandatoryKeywords);
      continue;
    } else if (argtype->internal == cpiIgnore) {
      v->argtype = argtype;
      nvalues++;
      continue;
    } else if (argtype->internal == cpiIgnoreRest) {
      if (nrequired < 0)
	nrequired = nvalues;
      ignore_rest = true;
      goto done;
    }

    // store stuff in v
    v->argtype = argtype;
    v->description = va_arg(val, const char *);
    if (argtype->flags & cpArgExtraInt)
      v->extra = va_arg(val, int);
    v->store_confirm = (confirm_keywords ? va_arg(val, bool *) : 0);
    v->store = va_arg(val, void *);
    if (argtype->flags & cpArgStore2)
      v->store2 = va_arg(val, void *);
    v->v.i = (mandatory_keywords ? -1 : 0);
    nvalues++;
  }

 done:
  
  if (nrequired < 0)
    nrequired = nvalues;
  if (npositional < 0)
    npositional = nvalues;
  
  return 0;
}

  
int
CpVaHelper::assign_keyword_argument(const String &arg)
{
  String keyword, rest;
  // extract keyword
  if (!cp_keyword(arg, &keyword, &rest))
    return kwNoKeyword;
  // doesn't count as a keyword if there was no accompanying data
  // (XXX is this a great idea?)
  if (!rest)
    return kwNoKeyword;
  // look for keyword value
  for (int i = npositional; i < nvalues; i++)
    if (keyword == cp_values[i].keyword) {
      cp_value *val = &cp_values[i];
      // handle special types differently
      if (special_argtype_for_keyword(val->argtype))
	return handle_special_argtype_for_keyword(val, rest);
      else {
	// do not report error if keyword used already
	// if (cp_values[i].v.i > 0)
	//   return kwDupKeyword;
	val->v.i = 1;
	val->v_string = rest;
	return kwSuccess;
      }
    }
  // no keyword found
  return kwUnkKeyword;
}

void
CpVaHelper::add_keyword_error(StringAccum &sa, int err, const String &arg,
			      const char *argname, int argno)
{
  if (err >= 0)
    return;
  if (sa.length())
    sa << ", ";
  if (err == kwNoKeyword)
    sa << '<' << argname << ' ' << (argno + 1) << '>';
  else {
    String keyword, rest;
    (void) cp_keyword(arg, &keyword, &rest);
    sa << keyword;
    if (err == kwDupKeyword)
      sa << " (duplicate keyword)";
  }
}

int
CpVaHelper::finish_keyword_error(const char *format, const char *bad_keywords, ErrorHandler *errh)
{
    StringAccum keywords_sa;
    for (int i = npositional; i < nvalues; i++) {
	if (i > npositional)
	    keywords_sa << ", ";
	keywords_sa << cp_values[i].keyword;
    }
    errh->error(format, bad_keywords, keywords_sa.c_str());
    return -EINVAL;
}

int
CpVaHelper::assign_arguments(const Vector<String> &args, const char *argname, ErrorHandler *errh)
{
  if (!cp_values || !cp_parameter_used)
    return errh->error("out of memory in cp_va_parse");

  //
  // Assign parameters from 'conf' to argument slots.
  //
  
  int npositional_supplied = 0;
  StringAccum keyword_error_sa;
  bool any_keywords = false;
  cp_parameter_used->assign(args.size(), 0);
  
  for (int i = 0; i < args.size(); i++) {
    // check for keyword if past mandatory positional arguments
    if (npositional_supplied >= nrequired) {
      int result = assign_keyword_argument(args[i]);
      if (result == kwSuccess) {
	(*cp_parameter_used)[i] = 1;
	any_keywords = true;
      } else if (!any_keywords)
	goto positional_argument; // optional argument, or too many arguments
      else if (ignore_rest)
	/* no error */;
      else
	add_keyword_error(keyword_error_sa, result, args[i], argname, i);
      continue;
    }

    // otherwise, assign positional argument
   positional_argument:
    if (npositional_supplied < npositional) {
      (*cp_parameter_used)[i] = 1;
      cp_values[npositional_supplied].v_string = args[i];
    }
    npositional_supplied++;
  }
  
  // report keyword argument errors
  if (keyword_error_sa.length() && !keywords_only)
    return finish_keyword_error("bad keyword(s) %s\n(valid keywords are %s)", keyword_error_sa.c_str(), errh);
  
  // report missing mandatory keywords
  for (int i = npositional; i < nvalues; i++)
    if (cp_values[i].v.i < 0) {
      if (keyword_error_sa.length())
	keyword_error_sa << ", ";
      keyword_error_sa << cp_values[i].keyword;
    }
  if (keyword_error_sa.length())
    return errh->error("missing mandatory keyword(s) %s", keyword_error_sa.c_str());
  
  // if wrong number of arguments, print signature
  if (npositional_supplied < nrequired
      || (npositional_supplied > npositional && !ignore_rest)) {
    StringAccum signature;
    for (int i = 0; i < npositional; i++) {
      if (i == nrequired)
	signature << (nrequired > 0 ? " [" : "[");
      if (i)
	signature << ", ";
      if (const cp_argtype *t = cp_values[i].argtype)
	signature << t->description;
      else
	signature << "??";
    }
    if (ignore_rest)
      signature << "...";
    if (nrequired < npositional)
      signature << "]";
    if (npositional < nvalues) {
      if (npositional)
	signature << ", ";
      signature << "[keywords]";
    }

    const char *whoops = (npositional_supplied > npositional ? "too many" : "too few");
    if (signature.length())
      errh->error("%s %ss; expected '%s'", whoops, argname, signature.c_str());
    else
      errh->error("expected empty %s list", argname);
    return -EINVAL;
  }

  // clear 'argtype' on unused arguments
  for (int i = npositional_supplied; i < npositional; i++)
    cp_values[i].argtype = 0;
  for (int i = npositional; i < nvalues; i++)
    if (cp_values[i].v.i <= 0)
      cp_values[i].argtype = 0;

  return 0;
}

int
CpVaHelper::parse_arguments(const char *argname,
#ifndef CLICK_TOOL
			    Element *context,
#endif
			    ErrorHandler *errh)
{
  int nerrors_in = errh->nerrors();
  
  // parse arguments
  char argname_buf[128];
  int argname_offset;
  argname_offset = sprintf(argname_buf, "%s ", argname);

  for (int i = 0; i < npositional; i++) {
    cp_value *v = &cp_values[i];
    if (v->argtype) {
      sprintf(argname_buf + argname_offset, "%d", i + 1);
      v->argtype->parse(v, v->v_string, errh, argname_buf  CP_PASS_CONTEXT);
    }
  }
  for (int i = npositional; i < nvalues; i++) {
    cp_value *v = &cp_values[i];
    if (v->argtype) {
      StringAccum sa;
      sa << "keyword " << v->keyword;
      v->argtype->parse(v, v->v_string, errh, sa.c_str()  CP_PASS_CONTEXT);
    }
  }

  // check for failure
  if (errh->nerrors() != nerrors_in)
    return -EINVAL;
  
  // if success, actually set the values
  int nset = 0;
  for (int i = 0; i < nvalues; i++)
    if (const cp_argtype *t = cp_values[i].argtype) {
      t->store(&cp_values[i]  CP_PASS_CONTEXT);
      nset++;
    }
  return nset;
}

}


int
cp_va_parse(const Vector<String> &argv,
#ifndef CLICK_TOOL
	    Element *context,
#endif
	    ErrorHandler *errh, ...)
{
  va_list val;
  va_start(val, errh);
  CpVaHelper cpva(cp_values, CP_VALUES_SIZE, false);
  int retval = cpva.develop_values(val, errh);
  if (retval >= 0)
    retval = cpva.assign_arguments(argv, "argument", errh);
  if (retval >= 0)
    retval = cpva.parse_arguments("argument"  CP_PASS_CONTEXT, errh);
  va_end(val);
  return retval;
}

int
cp_va_parse(const String &confstr,
#ifndef CLICK_TOOL
	    Element *context,
#endif
	    ErrorHandler *errh, ...)
{
  va_list val;
  va_start(val, errh);
  Vector<String> argv;
  cp_argvec(confstr, argv);
  CpVaHelper cpva(cp_values, CP_VALUES_SIZE, false);
  int retval = cpva.develop_values(val, errh);
  if (retval >= 0)
    retval = cpva.assign_arguments(argv, "argument", errh);
  if (retval >= 0)
    retval = cpva.parse_arguments("argument"  CP_PASS_CONTEXT, errh);
  va_end(val);
  return retval;
}

int
cp_va_space_parse(const String &arg,
#ifndef CLICK_TOOL
		  Element *context,
#endif
		  ErrorHandler *errh, ...)
{
  va_list val;
  va_start(val, errh);
  Vector<String> argv;
  cp_spacevec(arg, argv);
  CpVaHelper cpva(cp_values, CP_VALUES_SIZE, false);
  int retval = cpva.develop_values(val, errh);
  if (retval >= 0)
    retval = cpva.assign_arguments(argv, "word", errh);
  if (retval >= 0)
    retval = cpva.parse_arguments("word"  CP_PASS_CONTEXT, errh);
  va_end(val);
  return retval;
}

int
cp_va_parse_keyword(const String &arg,
#ifndef CLICK_TOOL
		    Element *context,
#endif
		    ErrorHandler *errh, ...)
{
  va_list val;
  va_start(val, errh);
  Vector<String> argv;
  argv.push_back(arg);
  CpVaHelper cpva(cp_values, CP_VALUES_SIZE, true);
  int retval = cpva.develop_values(val, errh);
  if (retval >= 0)
    retval = cpva.assign_arguments(argv, "argument", errh);
  if (retval >= 0)
    retval = cpva.parse_arguments("argument"  CP_PASS_CONTEXT, errh);
  va_end(val);
  return retval;
}

int
cp_va_parse_remove_keywords(Vector<String> &argv, int first,
#ifndef CLICK_TOOL
			    Element *context,
#endif
			    ErrorHandler *errh, ...)
{
  Vector<String> conf2, *confp = &argv;
  if (first > 0) {
    for (int i = first; i < argv.size(); i++)
      conf2.push_back(argv[i]);
    confp = &conf2;
  }
  
  va_list val;
  va_start(val, errh);
  CpVaHelper cpva(cp_values, CP_VALUES_SIZE, true);
  int retval = cpva.develop_values(val, errh);
  if (retval >= 0)
    retval = cpva.assign_arguments(*confp, "argument", errh);
  if (retval >= 0)
    retval = cpva.parse_arguments("argument"  CP_PASS_CONTEXT, errh);
  va_end(val);

  // remove keywords that were used
  if (retval >= 0) {
    int delta = 0;
    for (int i = first; i < argv.size(); i++)
      if ((*cp_parameter_used)[i - first])
	delta++;
      else if (delta)
	argv[i - delta] = argv[i];
    argv.resize(argv.size() - delta);
  }
  
  return retval;
}

int
cp_assign_arguments(const Vector<String> &argv, const Vector<String> &keys, Vector<String> *values)
{
  // check common case
  if (keys.size() == 0 || !keys.back()) {
    if (argv.size() != keys.size())
      return -EINVAL;
    else {
      if (values)
	*values = argv;
      return 0;
    }
  }

  if (!cp_values || !cp_parameter_used || keys.size() > CP_VALUES_SIZE)
    return -ENOMEM; /*errh->error("out of memory in cp_va_parse");*/

  CpVaHelper cpva(cp_values, CP_VALUES_SIZE, false);
  if (keys.size() && keys.back() == "__REST__") {
    cpva.ignore_rest = true;
    cpva.nvalues = keys.size() - 1;
  } else {
    cpva.ignore_rest = false;
    cpva.nvalues = keys.size();
  }
  
  int arg;
  for (arg = 0; arg < cpva.nvalues && keys[arg] == ""; arg++) {
    cp_values[arg].argtype = 0;
    cp_values[arg].keyword = 0;
  }
  cpva.nrequired = cpva.npositional = arg;
  for (; arg < cpva.nvalues; arg++) {
    cp_values[arg].argtype = 0;
    cp_values[arg].keyword = keys[arg].c_str();
    cp_values[arg].v.i = -1;	// mandatory keyword
  }

  int retval = cpva.assign_arguments(argv, "argument", ErrorHandler::silent_handler());
  if (retval >= 0 && values) {
    if (cpva.ignore_rest) {
      // collect '__REST__' argument
      StringAccum sa;
      bool any = false;
      for (int i = 0; i < argv.size(); i++)
	if (!(*cp_parameter_used)[i]) {
	  sa << (any ? ", " : "") << argv[i];
	  any = true;
	}
      cp_values[cpva.nvalues].v_string = sa.take_string();
    }
    values->resize(keys.size());
    for (arg = 0; arg < keys.size(); arg++)
      (*values)[arg] = cp_values[arg].v_string;
  }
  return retval;
}


// UNPARSING

String
cp_unparse_bool(bool b)
{
    if (b)
	return String::stable_string("true", 4);
    else
	return String::stable_string("false", 5);
}

#ifdef HAVE_INT64_TYPES

String
cp_unparse_unsigned64(uint64_t q, int base, bool uppercase)
{
  // Unparse a uint64_t. Linux kernel sprintf can't handle %lld, so we provide
  // our own function.
  
  char buf[256];
  char *lastbuf = buf + 255;
  char *trav;
  
  if (base == 16 || base == 8) {
    // different code.
    const char *digits = (uppercase ? "0123456789ABCDEF" : "0123456789abcdef");
    int shift = (base == 16 ? 4 : 3);
    for (trav = lastbuf; q > 0; trav--) {
      *trav = digits[q & (base - 1)];
      q >>= shift;
    }
  } else {
    assert(base == 10);
    
    for (trav = lastbuf; q > 0; trav--) {
      
      // k = Approx[q/10] -- know that k <= q/10
      uint64_t k = (q >> 4) + (q >> 5) + (q >> 8) + (q >> 9)
	+ (q >> 12) + (q >> 13) + (q >> 16) + (q >> 17);
      uint64_t m;
      
      // increase k until it exactly equals floor(q/10). on exit, m is the
      // remainder: m < 10 and q == 10*k + m.
      while (1) {
	// d = 10*k
	uint64_t d = (k << 3) + (k << 1);
	m = q - d;
	if (m < 10) break;
	
	// delta = Approx[m/10] -- know that delta <= m/10
	uint64_t delta = (m >> 4) + (m >> 5) + (m >> 8) + (m >> 9);
	if (m >= 0x1000)
	  delta += (m >> 12) + (m >> 13) + (m >> 16) + (m >> 17);
	
	// delta might have underflowed: add at least 1
	k += (delta ? delta : 1);
      }
      
      *trav = '0' + (unsigned)m;
      q = k;
    }
  }
  
  // make sure at least one 0 is written
  if (trav == lastbuf)
    *trav-- = '0';
  
  return String(trav + 1, lastbuf - trav);
}

String
cp_unparse_integer64(int64_t q, int base, bool uppercase)
{
  if (q < 0)
    return "-" + cp_unparse_unsigned64(-q, base, uppercase);
  else
    return cp_unparse_unsigned64(q, base, uppercase);
}

#endif

String
cp_unparse_real2(uint32_t real, int frac_bits)
{
  // adopted from Knuth's TeX function print_scaled, hope it is correct in
  // this context but not sure.
  // Works well with cp_real2 above; as an invariant,
  // unsigned x, y;
  // cp_real2(cp_unparse_real2(x, FRAC_BITS), FRAC_BITS, &y) == true && x == y
  
  StringAccum sa;
  assert(frac_bits <= CP_REAL2_MAX_FRAC_BITS);

  uint32_t int_part = real >> frac_bits;
  sa << int_part;

  uint32_t one = 1 << frac_bits;
  real &= one - 1;
  if (!real) return sa.take_string();
  
  sa << ".";
  real = (10 * real) + 5;
  unsigned allowable_inaccuracy = 10;

  unsigned inaccuracy_rounder = 5;
  while (inaccuracy_rounder < (one >> 1))
    inaccuracy_rounder *= 10;
  
  do {
    if (allowable_inaccuracy > one)
      real += (one >> 1) - inaccuracy_rounder;
    sa << static_cast<char>('0' + (real >> frac_bits));
    real = 10 * (real & (one - 1));
    allowable_inaccuracy *= 10;
  } while (real > allowable_inaccuracy);

  return sa.take_string();
}

#undef TEST_REAL2
#ifdef TEST_REAL2
void
test_unparse_real2()
{
#define TEST(s, frac_bits, result) { String q = (#s); uint32_t r; if (!cp_unsigned_real2(q, (frac_bits), &r)) fprintf(stderr, "FAIL: %s unparsable\n", q.c_str()); else { String qq = cp_unparse_real2(r, (frac_bits)); fprintf(stderr, "%s: %s %d/%d %s\n", (qq == (result) ? "PASS" : "FAIL"), q.c_str(), r, (frac_bits), qq.c_str()); }}
  TEST(0.418, 8, "0.418");
  TEST(0.417, 8, "0.418");
  TEST(0.416, 8, "0.414");
  TEST(0.42, 8, "0.42");
  TEST(0.3, 16, "0.3");
  TEST(0.49, 16, "0.49");
  TEST(0.499, 16, "0.499");
  TEST(0.4999, 16, "0.4999");
  TEST(0.49999, 16, "0.49998");
  TEST(0.499999, 16, "0.5");
  TEST(0.49998, 16, "0.49998");
  TEST(0.999999, 16, "1");
#undef TEST
}
#endif

String
cp_unparse_real2(int32_t real, int frac_bits)
{
  if (real < 0)
    return "-" + cp_unparse_real2(static_cast<uint32_t>(-real), frac_bits);
  else
    return cp_unparse_real2(static_cast<uint32_t>(real), frac_bits);
}

#ifdef HAVE_INT64_TYPES
String
cp_unparse_real2(uint64_t real, int frac_bits)
{
  assert(frac_bits <= CP_REAL2_MAX_FRAC_BITS);
  String int_part = cp_unparse_unsigned64(real >> frac_bits, 10, false);
  String frac_part = cp_unparse_real2((uint32_t)(real & ((1 << frac_bits) - 1)), frac_bits);
  return int_part + frac_part.substring(1);
}

String
cp_unparse_real2(int64_t real, int frac_bits)
{
  if (real < 0)
    return "-" + cp_unparse_real2(static_cast<uint64_t>(-real), frac_bits);
  else
    return cp_unparse_real2(static_cast<uint64_t>(real), frac_bits);
}
#endif

String
cp_unparse_real10(uint32_t real, int frac_digits)
{
  assert(frac_digits >= 0 && frac_digits <= 9);
  uint32_t one = exp10val[frac_digits];
  uint32_t int_part = real / one;
  uint32_t frac_part = real - (int_part * one);

  if (frac_part == 0)
    return String(int_part);

  StringAccum sa(30);
  sa << int_part << '.';
  if (char *x = sa.extend(frac_digits, 1))
    sprintf(x, "%0*d", frac_digits, frac_part);
  else				// out of memory
    return sa.take_string();

  // remove trailing zeros from fraction part
  while (sa.back() == '0')
    sa.pop_back();

  return sa.take_string();
}

String
cp_unparse_real10(int32_t real, int frac_digits)
{
    if (real < 0)
	return "-" + cp_unparse_real10(static_cast<uint32_t>(-real), frac_digits);
    else
	return cp_unparse_real10(static_cast<uint32_t>(real), frac_digits);
}

String
cp_unparse_milliseconds(uint32_t ms)
{
    if (ms && ms < 1000)
	return String(ms) + "ms";
    else
	return cp_unparse_real10(ms, 3) + "s";
}

String
cp_unparse_microseconds(uint32_t us)
{
    if (us && us < 1000)
	return String(us) + "us";
    else
	return cp_unparse_real10(us, 6) + "s";
}

String
cp_unparse_interval(const Timestamp& ts)
{
    if (ts.sec() == 0)
	return cp_unparse_microseconds(ts.usec());
    else {
	StringAccum sa;
	sa << ts << 's';
	return sa.take_string();
    }
}

String
cp_unparse_interval(const timeval& tv)
{
    return cp_unparse_interval(*(const Timestamp*) &tv);
}

String
cp_unparse_bandwidth(uint32_t bw)
{
    if (bw >= 0x20000000U)
	return cp_unparse_real10(bw, 6) + "MBps";
    else if (bw >= 125000000)
	return cp_unparse_real10(bw * 8, 9) + "Gbps";
    else if (bw >= 125000)
	return cp_unparse_real10(bw * 8, 6) + "Mbps";
    else
	return cp_unparse_real10(bw * 8, 3) + "kbps";
}


// initialization and cleanup

void
cp_va_static_initialize()
{
    assert(!cp_values);
  
    cp_register_argtype(cpOptional, "<optional arguments marker>", 0, default_parsefunc, default_storefunc, cpiOptional);
    cp_register_argtype(cpKeywords, "<keyword arguments marker>", 0, default_parsefunc, default_storefunc, cpiKeywords);
    cp_register_argtype(cpConfirmKeywords, "<confirmed keyword arguments marker>", 0, default_parsefunc, default_storefunc, cpiConfirmKeywords);
    cp_register_argtype(cpMandatoryKeywords, "<mandatory keyword arguments marker>", 0, default_parsefunc, default_storefunc, cpiMandatoryKeywords);
    cp_register_argtype(cpIgnore, "<ignored argument>", 0, default_parsefunc, default_storefunc, cpiIgnore);
    cp_register_argtype(cpIgnoreRest, "<ignore rest marker>", 0, default_parsefunc, default_storefunc, cpiIgnoreRest);
  
    cp_register_argtype(cpArgument, "arg", 0, default_parsefunc, default_storefunc, cpiArgument);
    cp_register_argtype(cpArguments, "args", 0, default_parsefunc, default_storefunc, cpiArguments);
    cp_register_argtype(cpString, "string", 0, default_parsefunc, default_storefunc, cpiString);
    cp_register_argtype(cpWord, "word", 0, default_parsefunc, default_storefunc, cpiWord);
    cp_register_argtype(cpKeyword, "keyword", 0, default_parsefunc, default_storefunc, cpiKeyword);
    cp_register_argtype(cpBool, "bool", 0, default_parsefunc, default_storefunc, cpiBool);
    cp_register_argtype(cpByte, "byte", 0, default_parsefunc, default_storefunc, cpiByte);
    cp_register_argtype(cpShort, "short", 0, default_parsefunc, default_storefunc, cpiShort);
    cp_register_argtype(cpUnsignedShort, "unsigned short", 0, default_parsefunc, default_storefunc, cpiUnsignedShort);
    cp_register_argtype(cpInteger, "int", 0, default_parsefunc, default_storefunc, cpiInteger);
    cp_register_argtype(cpUnsigned, "unsigned", 0, default_parsefunc, default_storefunc, cpiUnsigned);
    cp_register_argtype(cpNamedInteger, "named int", cpArgExtraInt, default_parsefunc, default_storefunc, cpiNamedInteger);
#ifdef HAVE_INT64_TYPES
    cp_register_argtype(cpInteger64, "64-bit int", 0, default_parsefunc, default_storefunc, cpiInteger64);
    cp_register_argtype(cpUnsigned64, "64-bit unsigned", 0, default_parsefunc, default_storefunc, cpiUnsigned64);
#endif
#ifdef CLICK_USERLEVEL
    cp_register_argtype(cpFileOffset, "file offset", 0, default_parsefunc, default_storefunc, cpiFileOffset);
#endif
    cp_register_argtype(cpReal2, "real", cpArgExtraInt, default_parsefunc, default_storefunc, cpiReal2);
    cp_register_argtype(cpUnsignedReal2, "unsigned real", cpArgExtraInt, default_parsefunc, default_storefunc, cpiUnsignedReal2);
    cp_register_argtype(cpReal10, "real", cpArgExtraInt, default_parsefunc, default_storefunc, cpiReal10);
    cp_register_argtype(cpUnsignedReal10, "unsigned real", cpArgExtraInt, default_parsefunc, default_storefunc, cpiUnsignedReal10);
#ifdef HAVE_FLOAT_TYPES
    cp_register_argtype(cpDouble, "double", 0, default_parsefunc, default_storefunc, cpiDouble);
#endif
    cp_register_argtype(cpSeconds, "time in sec", 0, default_parsefunc, default_storefunc, cpiSeconds);
    cp_register_argtype(cpSecondsAsMilli, "time in sec (msec precision)", 0, default_parsefunc, default_storefunc, cpiSecondsAsMilli);
    cp_register_argtype(cpSecondsAsMicro, "time in sec (usec precision)", 0, default_parsefunc, default_storefunc, cpiSecondsAsMicro);
    cp_register_argtype(cpTimeval, "seconds since the epoch", 0, default_parsefunc, default_storefunc, cpiTimeval);
    cp_register_argtype(cpTimestamp, "seconds since the epoch", 0, default_parsefunc, default_storefunc, cpiTimeval);
    cp_register_argtype(cpInterval, "time in sec (usec precision)", 0, default_parsefunc, default_storefunc, cpiInterval);
    cp_register_argtype(cpBandwidth, "bandwidth", 0, default_parsefunc, default_storefunc, cpiBandwidth);
    cp_register_argtype(cpIPAddress, "IP address", 0, default_parsefunc, default_storefunc, cpiIPAddress);
    cp_register_argtype(cpIPPrefix, "IP address prefix", cpArgStore2, default_parsefunc, default_storefunc, cpiIPPrefix);
    cp_register_argtype(cpIPAddressOrPrefix, "IP address or prefix", cpArgStore2, default_parsefunc, default_storefunc, cpiIPAddressOrPrefix);
    cp_register_argtype(cpIPAddressList, "list of IP addresses", 0, default_parsefunc, default_storefunc, cpiIPAddressList);
    cp_register_argtype(cpEthernetAddress, "Ethernet address", 0, default_parsefunc, default_storefunc, cpiEthernetAddress);
    cp_register_argtype(cpTCPPort, "TCP port", 0, default_parsefunc, default_storefunc, cpiTCPPort);
    cp_register_argtype(cpUDPPort, "UDP port", 0, default_parsefunc, default_storefunc, cpiUDPPort);
#ifndef CLICK_TOOL
    cp_register_argtype(cpElement, "element name", 0, default_parsefunc, default_storefunc, cpiElement);
    cp_register_argtype(cpHandlerName, "handler name", cpArgStore2, default_parsefunc, default_storefunc, cpiHandlerName);
    cp_register_argtype(cpReadHandlerCall, "read handler name", 0, default_parsefunc, default_storefunc, cpiReadHandlerCall);
    cp_register_argtype(cpWriteHandlerCall, "write handler name and value", 0, default_parsefunc, default_storefunc, cpiWriteHandlerCall);
#endif
#ifdef HAVE_IP6
    cp_register_argtype(cpIP6Address, "IPv6 address", 0, default_parsefunc, default_storefunc, cpiIP6Address);
    cp_register_argtype(cpIP6Prefix, "IPv6 address prefix", cpArgStore2, default_parsefunc, default_storefunc, cpiIP6Prefix);
    cp_register_argtype(cpIP6AddressOrPrefix, "IPv6 address or prefix", cpArgStore2, default_parsefunc, default_storefunc, cpiIP6AddressOrPrefix);
#endif
#ifdef HAVE_IPSEC
    cp_register_argtype(cpDesCblock, "DES cipher block", 0, default_parsefunc, default_storefunc, cpiDesCblock);
#endif
#ifdef CLICK_USERLEVEL
    cp_register_argtype(cpFilename, "filename", 0, default_parsefunc, default_storefunc, cpiFilename);
#endif

    cp_values = new cp_value[CP_VALUES_SIZE];
    cp_parameter_used = new Vector<int>;

#ifdef TEST_REAL2
    test_unparse_real2();
#endif

#if CLICK_USERLEVEL && HAVE_IP6
    // force all IP6 library objects to be included
    extern int IP6FlowID_linker_trick;
    IP6FlowID_linker_trick++;
#endif
}

void
cp_va_static_cleanup()
{
  for (int i = 0; i < NARGTYPE_HASH; i++) {
    cp_argtype *t = argtype_hash[i];
    while (t) {
      cp_argtype *n = t->next;
      delete t;
      t = n;
    }
  }
  memset(argtype_hash, 0, sizeof(argtype_hash));

  delete[] cp_values;
  delete cp_parameter_used;
  cp_values = 0;
  cp_parameter_used = 0;
}

CLICK_ENDDECLS


syntax highlighted by Code2HTML, v. 0.9.1