/*++
/* NAME
/*	safe_ultostr 3
/* SUMMARY
/*	convert unsigned long to safe string
/* SYNOPSIS
/*	#include <safe_ultostr.h>
/*
/*	char	*safe_ultostr(result, ulval, base, padlen, padchar)
/*	VSTRING	*result;
/*	unsigned long ulval;
/*	int	base;
/*	int	padlen;
/*	int	padchar;
/*
/*	unsigned long safe_strtoul(start, end, base)
/*	const char *start;
/*	char **end;
/*	int	base;
/* DESCRIPTION
/*	The functions in this module perform conversions between
/*	unsigned long values and "safe" alphanumerical strings
/*	(strings with digits, uppercase letters and lowercase
/*	letters, but without the vowels AEIOUaeiou). Specifically,
/*	the characters B-Z represent the numbers 10-30, and b-z
/*	represent 31-51.
/*
/*	safe_ultostr() converts an unsigned long value to a safe
/*	alphanumerical string. This is the reverse of safe_strtoul().
/*
/*	safe_strtoul() implements similar functionality as strtoul()
/*	except that it uses a safe alphanumerical string as input,
/*	and that it supports no signs or 0/0x prefixes.
/*
/*	Arguments:
/* .IP result
/*	Buffer for storage of the result of conversion to string.
/* .IP ulval
/*	Unsigned long value.
/* .IP base
/*	Value between 2 and 52 inclusive.
/* .IP padlen
/* .IP padchar
/*	Left-pad a short result with padchar characters to the
/*	specified length.  Specify padlen=0 to disable padding.
/* .IP start
/*	Pointer to the first character of the string to be converted.
/* .IP end
/*	On return, pointer to the first character not in the input
/*	alphabet, or to the string terminator.
/* DIAGNOSTICS
/*	Fatal: out of memory.
/*
/*	safe_strtoul() returns (0, EINVAL) when no conversion could
/*	be performed, and (ULONG_MAX, ERANGE) in case of overflow.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <ctype.h>

/* Utility library. */

#include <msg.h>
#include <vstring.h>
#include <mymalloc.h>

/* Global library. */

#include <safe_ultostr.h>

/* Application-specific. */

#define STR	vstring_str
#define END	vstring_end
#define SWAP(type, a, b) { type temp; temp = a; a = b; b = temp; }

static unsigned char safe_chars[] =
"0123456789BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz";

#define SAFE_MAX_BASE	(sizeof(safe_chars) - 1)
#define SAFE_MIN_BASE	(2)

/* safe_ultostr - convert unsigned long to safe alphanumerical string */

char   *safe_ultostr(VSTRING *buf, unsigned long ulval, int base,
		             int padlen, int padchar)
{
    const char *myname = "safe_ultostr";
    char   *start;
    char   *last;
    int     i;

    /*
     * Sanity check.
     */
    if (base < SAFE_MIN_BASE || base > SAFE_MAX_BASE)
	msg_panic("%s: bad base: %d", myname, base);

    /*
     * First accumulate the result, backwards.
     */
    VSTRING_RESET(buf);
    while (ulval != 0) {
	VSTRING_ADDCH(buf, safe_chars[ulval % base]);
	ulval /= base;
    }
    while (VSTRING_LEN(buf) < padlen)
	VSTRING_ADDCH(buf, padchar);
    VSTRING_TERMINATE(buf);

    /*
     * Then, reverse the result.
     */
    start = STR(buf);
    last = END(buf) - 1;
    for (i = 0; i < VSTRING_LEN(buf) / 2; i++)
	SWAP(int, start[i], last[-i]);
    return (STR(buf));
}

/* safe_strtoul - convert safe alphanumerical string to unsigned long */

unsigned long safe_strtoul(const char *start, char **end, int base)
{
    const char *myname = "safe_strtoul";
    static unsigned char *char_map = 0;
    unsigned char *cp;
    unsigned long sum;
    unsigned long div_limit;
    unsigned long mod_limit;
    int     char_val;

    /*
     * Sanity check.
     */
    if (base < SAFE_MIN_BASE || base > SAFE_MAX_BASE)
	msg_panic("%s: bad base: %d", myname, base);

    /*
     * One-time initialization. Assume 8-bit bytes.
     */
    if (char_map == 0) {
	char_map = (unsigned char *) mymalloc(256);
	for (char_val = 0; char_val < 256; char_val++)
	    char_map[char_val] = SAFE_MAX_BASE;
	for (char_val = 0; char_val < SAFE_MAX_BASE; char_val++)
	    char_map[safe_chars[char_val]] = char_val;
    }

    /*
     * Per-call initialization.
     */
    sum = 0;
    div_limit = ULONG_MAX / base;
    mod_limit = ULONG_MAX % base;

    /*
     * Skip leading whitespace. We don't implement sign/base prefixes.
     */
    if (end)
	*end = (char *) start;
    while (ISSPACE(*start))
	++start;

    /*
     * Start the conversion.
     */
    errno = 0;
    for (cp = (unsigned char *) start; (char_val = char_map[*cp]) < base; cp++) {
	/* Return (ULONG_MAX, ERANGE) if the result is too large. */
	if (sum > div_limit
	    || (sum == div_limit && char_val > mod_limit)) {
	    sum = ULONG_MAX;
	    errno = ERANGE;
	    /* Skip "valid" characters, per the strtoul() spec. */
	    while (char_map[*++cp] < base)
		 /* void */ ;
	    break;
	}
	sum = sum * base + char_val;
    }
    /* Return (0, EINVAL) after no conversion. Test moved here 20131209. */
    if (cp == (unsigned char *) start)
	errno = EINVAL;
    else if (end)
	*end = (char *) cp;
    return (sum);
}

#ifdef TEST

 /*
  * Proof-of-concept test program. Read a number from stdin, convert to
  * string, and print the result.
  */
#include <stdio.h>			/* sscanf */
#include <vstream.h>
#include <vstring_vstream.h>

int     main(int unused_argc, char **unused_argv)
{
    VSTRING *buf = vstring_alloc(100);
    char   *junk;
    unsigned long ulval;
    int     base;
    char    ch;
    unsigned long ulval2;

#ifdef MISSING_STRTOUL
#define strtoul strtol
#endif

    /*
     * Hard-coded string-to-number test.
     */
    ulval2 = safe_strtoul("  ", &junk, 10);
    if (*junk == 0 || errno != EINVAL)
	msg_warn("input=' ' result=%lu errno=%m", ulval2);

    /*
     * Configurable number-to-string-to-number test.
     */
    while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
	ch = 0;
	if (sscanf(STR(buf), "%lu %d%c", &ulval, &base, &ch) != 2 || ch) {
	    msg_warn("bad input %s", STR(buf));
	} else {
	    (void) safe_ultostr(buf, ulval, base, 5, '0');
	    vstream_printf("%lu = %s\n", ulval, STR(buf));
	    ulval2 = safe_strtoul(STR(buf), &junk, base);
	    if (*junk || (ulval2 == ULONG_MAX && errno == ERANGE))
		msg_warn("%s: %m", STR(buf));
	    if (ulval2 != ulval)
		msg_warn("%lu != %lu", ulval2, ulval);
	}
	vstream_fflush(VSTREAM_OUT);
    }
    vstring_free(buf);
    return (0);
}

#endif
