/*
 * tnmMibUtil.c --
 *
 *	Provides functions to search in the MIB tree.
 *
 * Copyright (c) 1994-1996 Technical University of Braunschweig.
 * Copyright (c) 1996-1997 University of Twente.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tnmSnmp.h"
#include "tnmMib.h"

/*
 * Global variables of the MIB extension:
 */

char *tnmMibFileName = NULL;		/* Current MIB file name loaded.   */
char *tnmMibModuleName = NULL;		/* Current MIB module name loaded. */
TnmMibNode *tnmMibTree = NULL;		/* The root of the MIB tree.	   */
TnmMibType *tnmMibTypeList = NULL;	/* List of textual conventions.	   */
TnmMibType *tnmMibTypeSaveMark = NULL;	/* The first already saved	   */
					/* element of tnmMibTypeList.	   */

TnmTable tnmMibAccessTable[] = {
    { TNM_MIB_NOACCESS,   "not-accessible" },
    { TNM_MIB_READONLY,   "read-only" },
    { TNM_MIB_READCREATE, "read-create" },
    { TNM_MIB_READWRITE,  "read-write" },
    { TNM_MIB_WRITEONLY,  "write-only" },
    { TNM_MIB_FORNOTIFY,  "accessible-for-notify" },
    { 0, NULL }
};

TnmTable tnmMibMacroTable[] = {
    { TNM_MIB_OBJECTTYPE,        "OBJECT-TYPE" },
    { TNM_MIB_OBJECTIDENTITY,    "OBJECT-IDENTITY" },
    { TNM_MIB_MODULEIDENTITY,    "MODULE-IDENTITY" },
    { TNM_MIB_NOTIFICATIONTYPE,  "NOTIFICATION-TYPE" },
    { TNM_MIB_TRAPTYPE,          "TRAP-TYPE" },
    { TNM_MIB_OBJECTGROUP,       "OBJECT-GROUP" },
    { TNM_MIB_NOTIFICATIONGROUP, "NOTIFICATION-GROUP" },
    { TNM_MIB_COMPLIANCE,        "MODULE-COMPLIANCE" },
    { TNM_MIB_CAPABILITIES,      "AGENT-CAPABILITIES" },
    { TNM_MIB_TEXTUALCONVENTION, "TEXTUAL-CONVENTION" },
    { 0, NULL }
};

/*
 * A private buffer that is used to assemble object identifier in 
 * dottet notation.
 */

static char oidBuffer[TNM_OID_MAX_SIZE * 8];

/*
 * Forward declarations for procedures defined later in this file:
 */

static char*
FormatOctetTC		_ANSI_ARGS_((char *val, char *fmt));

static char*
FormatIntTC		_ANSI_ARGS_((char *val, char *fmt));

static char*
FormatTimeTicks		_ANSI_ARGS_((char *val));

static char*
FormatOID		_ANSI_ARGS_((char *val));

static char*
ScanOctetTC		_ANSI_ARGS_((char *val, char *fmt));

static char*
ScanIntTC		_ANSI_ARGS_((char *val, char *fmt));

static char*
ScanTimeTicks		_ANSI_ARGS_((char *val));

static void
GetMibPath		_ANSI_ARGS_((TnmMibNode *nodePtr, char *soid));

static void
FormatUnsigned		_ANSI_ARGS_((unsigned u, char *s));


/*
 *----------------------------------------------------------------------
 *
 * FormatUnsigned --
 *
 *	This procedure formats the unsigned value in u into an 
 *	unsigned ascii string s. We avoid sprintf because this is 
 *	too slow.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
FormatUnsigned(u, s)
    u_int u;
    char *s;
{
    if (u < 10) {
	*s++ = '0' + u;
    } else {
	u_int t=10;
	char c = '0'+ (u % 10);
	u /= 10;
	while (u / t) t *= 10;
	while (t /= 10) *s++ = '0'+ (u / t) % 10;
	*s++ = c;
    }
    *s = '\0';
}

/*
 *----------------------------------------------------------------------
 *
 * GetMibPath --
 *
 *	This procedure writes the path to the given by nodePtr into
 *	the given character string. This is done recursively using the 
 *	pointers to the parent node.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
GetMibPath(nodePtr, s) 
    TnmMibNode *nodePtr;
    char *s;
{
    if (! nodePtr) return;
    if (nodePtr->parentPtr) {
	GetMibPath(nodePtr->parentPtr, s);
	while (*s) s++;
	*s++ = '.';
    }
    FormatUnsigned(nodePtr->subid, s);
}

/*
 *----------------------------------------------------------------------
 *
 * TnmMibNodeToOid --
 *
 *	This procedure retrieves the object identifier of the node 
 *	given by nodePtr. This is done recursively by going up to
 *	the root and afterwards assembling the object identifier.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TnmMibNodeToOid(nodePtr, oidPtr)
    TnmMibNode *nodePtr;
    TnmOid *oidPtr;
{
    if (! nodePtr) {
	TnmOidFree(oidPtr);
    }

    if (nodePtr->parentPtr) {
	TnmMibNodeToOid(nodePtr->parentPtr, oidPtr);
    }
    TnmOidAppend(oidPtr, nodePtr->subid);
}

/*
 *----------------------------------------------------------------------
 *
 * TnmMibGetOid --
 *
 *	This procedure returns the oid that belongs to label.
 *
 * Results:
 *	A pointer to a static string containing the object identifier
 *	in dotted notation or NULL if there is no such MIB node.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

char*
TnmMibGetOid(label)
    char *label;
{
    char *expanded = TnmHexToOid(label);
    TnmMibNode *nodePtr;
    int offset = -1;

    if (expanded) label = expanded;
    nodePtr = TnmMibFindNode(label, &offset, 0);
    if (nodePtr) {
	if (TnmIsOid(label)) {
	    return label;
	}
	GetMibPath(nodePtr, oidBuffer);
	if (offset > 0) {
	    strcat(oidBuffer, label+offset);
	}
	return oidBuffer;
    }

    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * TnmMibGetName --
 *
 *	This procedure returns the name that belongs to a descriptor.
 *	This is the reverse operation to TnmMibGetOid().
 *
 * Results:
 *	A pointer to a static string containing the object descriptor
 *	or NULL if there is no such MIB node.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

char*
TnmMibGetName(label, exact)
    char *label;
    int exact;
{
    char *expanded = TnmHexToOid(label);
    TnmMibNode *nodePtr;
    int offset = -1;
    
    if (expanded) label = expanded;
    nodePtr = TnmMibFindNode(label, &offset, exact);
    if (nodePtr) {
	if (offset > 0) {
	    strcpy(oidBuffer, nodePtr->label);
	    strcat(oidBuffer, label+offset);
	    return oidBuffer;
	} else {
	    return nodePtr->label;
	}
    }

    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * TnmMibGetString --
 *
 * 	This procedure reads the next quoted string from the given
 *	file starting at the given file offset. White spaces following
 *	newline characters are removed.
 *
 * Results:
 *	A pointer to a static string containing the string.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

char*
TnmMibGetString(fileName, fileOffset)
    char *fileName;
    int fileOffset;
{
    static Tcl_DString *result = NULL;
    FILE *fp;
    int	ch;
    
    if (result == NULL) {
	result = (Tcl_DString *) ckalloc(sizeof(Tcl_DString));
	Tcl_DStringInit(result);
    } else {
	Tcl_DStringFree(result);
    }

    /*
     * Ignore bogus arguments, open the file and seek to the beginning
     * of the quoted string (this allows some fuzz in the offset
     * value).
     */
    
    if (fileName == NULL || fileOffset <= 0) {
	return "";
    }

    fp = fopen(fileName, "rb");
    if (fp == NULL) {
	perror(fileName);
	return "";
    }
    if (fseek(fp, fileOffset, 0) < 0) {
	perror(fileName);
	return "";
    }
    while ((ch = getc(fp)) != EOF) {
	if (ch == '"') break;
    }

    /*
     * Copy the string into the buffer until we find the terminating
     * quote character. Ignore all the white spaces at the beginning
     * of a new line so that the result is independent from the actual
     * MIB format.
     */

    for (ch = getc(fp); ch != EOF && ch != '"'; ch = getc(fp)) {
	char c = ch;
	Tcl_DStringAppend(result, &c, 1);
	if (ch == '\n') {
	    while ((ch = getc(fp)) != EOF) {
		if (!isspace(ch)) break;
	    }
	    if (ch == EOF || ch == '"') break;
	    c = ch;
	    Tcl_DStringAppend(result, &c, 1);
	}
    }

    fclose(fp);
    return Tcl_DStringValue(result);
}

/*
 *----------------------------------------------------------------------
 *
 * TnmMibGetBaseSyntax --
 *
 *	This procedure returns the transfer syntax actually used to 
 *	encode a value. This may be different from the syntax defined
 *	in the OBJECT-TYPE macro as textual conventions are based on 
 *	a set of primitive types. Note: We have sometimes more than 
 *	one MIB syntax tag for an ASN.1 type, so we must make sure to 
 *	find the right syntax tag.
 *
 * Results:
 *	The pointer to a static string containing the transfer syntax
 *	name or NULL if there is no such MIB node.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TnmMibGetBaseSyntax(name)
    char *name;
{
    int syntax = ASN1_OTHER;
    TnmMibNode *nodePtr = TnmMibFindNode(name, NULL, 0);

    if (nodePtr) {
	if (nodePtr->typePtr && nodePtr->typePtr->name) {
	    syntax = nodePtr->typePtr->syntax;
	} else {
	    syntax = nodePtr->syntax;
	}
    }

    return syntax;
}

/*
 *----------------------------------------------------------------------
 *
 * FormatOctetTC --
 *
 *	This procedure formats the octet string value according to the 
 *	textual convention stored in fmt. Parts marked XXX are untested.
 *
 * Results:
 *	The pointer to a static string containing the formatted value
 *	or NULL if there is no such MIB node.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static char *
FormatOctetTC(val, fmt)
    char *val;
    char *fmt;
{
    int pfx, have_pfx;			/* counter prefix */
    char *last_fmt;			/* save ptr to last seen fmt */
    static char *ret = NULL;
    static int ret_len = 0;
    int idx;

    /*
     * Sanity check and the simple case:
     */

    if (! fmt) {
        return val;
    }

#if 0
    if (val[0] == '0' && val[1] == 'x') {
	val += 2;
    }
#endif

    if (strcmp(fmt, "1x:") == 0) {
        return val;
    }

    if (ret == NULL) {
        ret = ckalloc(ret_len = 100);
    }
    idx = 0;			/* idx into ret buffer */

    while (*fmt && *val) {

        last_fmt = fmt;		/* save for loops */

	/* scan prefix: */
	have_pfx = pfx = 0;
	while (*fmt && isdigit(*fmt)) {
	    pfx = pfx * 10 + *fmt - '0', have_pfx = 1, fmt++;
	}
	if (! have_pfx) {
	    pfx = 1;
	}

	/* scan format: */
	if (*fmt == 'a') {

	    while (*val && pfx > 0) {
	        char c = *val++ & 0xff;
		int v = c >= 'a' ?  c - 87 : (c >= 'A' ? c - 55 : c - 48);
		if (! *val) {
		    break;
		}
		c = *val++ & 0xff;
		v = (v << 4) + (c >= 'a' ?  c - 87 :
				(c >= 'A' ? c - 55 : c - 48));
		if (idx + 100 >= ret_len) {
		    ret = ckrealloc(ret, ret_len += 100);
		}
		ret [idx++] = v;
		pfx--;
		if (*val == ':') {
		    val++;
		}
	    }
	    fmt++;

	} else if (*fmt == 'd' || *fmt == 'o' || *fmt == 'b') {

	    char format = *fmt;
	    int c, vv = 0, v;

	    fmt++;

	    /* collect octets and format: */
	    while (pfx > 0) {
	        if (! *val) {
		    break;
		}
		/* collect next byte from octal buffer and move to vv: */
		c = *val++ & 0xff;
		v = c >= 'a' ?  c - 87 : 
		  (c >= 'A' ? c - 55 : c - 48);
		if (! *val) {
		    break;
		}
		c = *val++ & 0xff;
		v = (v << 4) + (c >= 'a' ?  c - 87 :
				(c >= 'A' ? c - 55 : c - 48));
		vv = vv * 256 + v;
		
		if (*val == ':') {
		    val++;
		}

		pfx--;
	    }
		
	    if (idx + 100 >= ret_len) {
	        ret = ckrealloc(ret, ret_len += 100);
	    }
	    if (format == 'd') {
	        sprintf(ret + idx, "%d", vv);
	        idx += strlen(ret + idx);
	    } else if (format == 'o') {
	        /* XXX: completely untested */
	        sprintf(ret + idx, "%o", vv);
		idx += strlen(ret + idx);
	    } else if (format == 'b') {
	        /* XXX: completely untested */
	        int i; 
		for (i = (sizeof(int) * 8 - 1); i >= 0
		     && ! (vv & (1 << i)); i--);
		for (; i >= 0; i--)
		  ret [idx++] = vv & (1 << i) ? '1' : '0';
	    }
	} else if (*fmt == 'x') {

	    /* 
	     * A simple one: copy the following hex-digits:
	     */

	    while (pfx > 0) {		
	        if (! *val || ! val [1]) {
		    break;
		}
	        if (idx + 100 >= ret_len) {
		    ret = ckrealloc(ret, ret_len += 100);
	        }
		ret [idx++] = *val++;
		ret [idx++] = *val++;
		if (*val == ':') {
		    val++;
		}
		pfx--;
	    }
	    fmt++;
	} else {
	    fprintf(stderr, "scotty: unknown textual-format `%c'\n", *fmt); 
	    fmt++;
	}

	/*
	 * Look about a separator:
	 */

	if (*fmt && ! isdigit(*fmt) && *fmt != '*') {
	    if (have_pfx && *val) {
	        if (idx + 100 >= ret_len) {
		    ret = ckrealloc(ret, ret_len += 100);
		}
		ret [idx++] = *fmt;
	    }
	    fmt++;
	}

	/*
	 * Repeat with last format, if data is still available:
	 */

	if (! *fmt && *val) {
	    fmt = last_fmt;
	}
    }

    ret [idx] = 0;

    return ret;
}

/*
 *----------------------------------------------------------------------
 *
 * FormatIntTC --
 *
 *	This procedure formats the integer value according to the 
 *	textual convention stored in fmt. Parts marked XXX are 
 *	untested.
 *
 * Results:
 *	The pointer to a static string containing the formatted value
 *	or NULL if there is no such MIB node.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static char*
FormatIntTC(val, fmt)
    char *val;
    char *fmt;
{
    static char *ret = NULL;
    static int ret_len = 0;
    int i, idx, val_len;
    int dpt = -1;				/* decimal point */
    char format;

    /* 
     * Sanity check and the simple case:
     */

    if (! fmt) {
        return NULL;
    } else {
        format = *fmt;
    }

    if (*fmt == 'd' && ! fmt [1]) {
        return val;
    }

    /* 
     * Must be one of these: 
     */

    if (*fmt != 'd' && *fmt != 'x' && *fmt != 'o' && *fmt != 'b') {
        return NULL;
    }
    
    if (ret == NULL) {
        ret = ckalloc(ret_len = 100);
    }
    idx = 0;				/* idx into ret buffer */
	  
    /*
     * `d' format may have the form: d-[0-9]+ 
     */

    if (*fmt == 'd' && fmt [1] == '-' 
	&& fmt [2] >= '0' && fmt [2] <= '9') {
        format = *fmt;
	dpt = 0;
	for (fmt += 2; *fmt >= '0' && *fmt <= '9'; fmt++) {
	    dpt = dpt * 10 + *fmt - '0';
	}
    } else if (fmt [1]) {
        return NULL;				/* invalid */
    }

    /*
     * Check integer value;
     */

    for (val_len = 0; val [val_len]; val_len++) {
        if (! ((! val_len && val [val_len] == '-') || isdigit(val[val_len]))) {
	    return NULL;
	}
    }
    
    if (dpt >= 0) {

        /*
         * Now we have to format val_len digits with 
	 * decimal-point at dpt:
	 */

        if (dpt + val_len + 2 >= ret_len) {	/* don't care */
	     ret = ckrealloc(ret, ret_len = dpt + val_len + 2);
	}
	
	if (format == 'd') {
	    /* monadic - always first: */
	    if (*val == '-') {
	        ret [idx++] = '-', val++, val_len--;
	    }
	    if (dpt >= val_len) {
	        ret [idx++] = '0', ret [idx++] = '.';
		for (i = 0; i < dpt - val_len; i++) {
		    ret [idx++] = '0';
		}
		strcpy(ret + idx, val);
		return ret;
	    }
	    for (i = 0; i < val_len - dpt; i++) {
	        ret [idx++] = val [i];
	    }
	    ret [idx++] = '.';
	    for (; i < val_len; i++) {
	        ret [idx++] = val [i];
	    }
	} else if (format == 'o') {
	    /* XXX: completely untested */
	    sprintf(ret, "%o", atoi(val));
	    return ret;
	} else if (format == 'x') {
	    /* XXX: completely untested */
	    sprintf(ret, "%x", atoi(val));
	    return ret;
        } else if (format == 'b') {
	    int vv = atoi(val);
	    /* XXX: completely untested */
	    for (i = (sizeof(int) * 8 - 1); i > 0
		 && ! (vv & (1 << i)); i--);
	    for (; i >= 0; i--) {
	        ret [idx++] = vv & (1 << i) ? '1' : '0';
	    }
	}
    } else {
        /* error: */
	return val;
    }
    
    ret [idx] = 0;
    return ret;
}

/*
 *----------------------------------------------------------------------
 *
 * FormatTimeTicks --
 *
 *	This procedure formats an unsigned number representing a
 *	TimeTicks value into a readable string representation.
 *
 * Results:
 *	The pointer to a static string containing the formatted value
 *	or NULL if there is no such MIB node.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static char*
FormatTimeTicks(value)
    char *value;
{
    u_int d, h, m, s, f;
    static char buf[80];

    d = 0;
    while (isdigit(*value)) {
	d = 10 * d + *value - '0';
	value++;
    }
    f = d % 100; d = d /100;
    s = d % 60;  d = d / 60;
    m = d % 60;  d = d / 60;
    h = d % 24;  d = d / 24;
    sprintf(buf, "%dd %2d:%02d:%02d.%02d", d, h, m, s, f);
    return buf;
}

/*
 *----------------------------------------------------------------------
 *
 * FormatOID --
 *
 *	This procedure formats an object identifier into a readable
 *	and unique string representation.
 *
 * Results:
 *	The pointer to a static string containing the formatted value
 *	or NULL if there is no such MIB node.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static char*
FormatOID(value)
    char *value;
{
    TnmMibNode *nodePtr = TnmMibFindNode(value, NULL, 1);
    static char *buffer = NULL;
    int len;

    if (! nodePtr || nodePtr->moduleName == NULL || nodePtr->label == NULL) {
	return value;
    }

    len = strlen(nodePtr->moduleName) + strlen(nodePtr->label) + 2;
    if (buffer) {
	buffer = ckrealloc(buffer, len);
    } else {
	buffer = ckalloc(len);
    }
    strcpy(buffer, nodePtr->moduleName);
    strcat(buffer, "!");
    strcat(buffer, nodePtr->label);
    return buffer;
}

/*
 *----------------------------------------------------------------------
 *
 * ScanOctetTC --
 *
 *	This procedure scans a string unsing the textual convention and
 *	returns the octet string representation of the value. Parts 
 *	marked XXX are untested.
 *
 * Results:
 *	The pointer to a static string containing the formatted value
 *	or NULL if there is no such MIB node.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static char*
ScanOctetTC(val, fmt)
    char *val;
    char *fmt;
{
    int pfx, have_pfx;			/* counter prefix */
    char *last_fmt;			/* save ptr to last seen fmt */
    static char *ret = NULL;
    static int ret_len = 0;
    int idx;

    /*
     * Sanity check and the simple case:
     */

    if (! fmt) {
        return val;
    }

    if (! ret) {
        ret = ckalloc(ret_len = 100);
    }

    *ret = '\0';
    idx = 0;			/* idx into ret buffer */

    if (strcmp(fmt, "1x:") == 0) {
	return val;
    }
    
    /* quick ``255a'' formatting: */

    if (strcmp(fmt, "255a") == 0) {
	while (*val) {
	    if (idx + 100 >= ret_len) {
		ret = ckrealloc(ret, ret_len += 100);
	    }
	    sprintf(ret + idx, "%02x", *val & 0xff);
	    idx += 2;
	    if (*++val) {
		ret [idx++] = ':';
	    }
	}
	return ret;
    }


    /* and now to something different: */

    while (*fmt && *val) {
	last_fmt = fmt;	/* save for loops */
	/* scan prefix: */
	have_pfx = pfx = 0;
	while (*fmt && isdigit(*fmt)) {
	    pfx = pfx * 10 + *fmt - '0', have_pfx = 1, fmt++;
	}
	if (! have_pfx) {
	    pfx = 1;
	}
	
	/* scan format: */
	if (*fmt == 'a') {
	    while (*val && pfx > 0) {
		char c = *val;
		int c1 = (c & 0xf0) >> 4;
		int c2 = c & 0x0f;
		if ((c1 += '0') > '9') c1 += 7;
		if ((c2 += '0') > '9') c2 += 7;
		
		if (idx + 100 >= ret_len) {
		    ret = ckrealloc(ret, ret_len += 100);
		}
		
		ret [idx++] = c1, ret [idx++] = c2;
		if (*++val) {
		    ret [idx++] = ':';
		}
		pfx--;
	    }
	    fmt++ ;
	} else if (*fmt == 'd' || *fmt == 'o' || *fmt == 'b') {
	    int vv = 0, v, got_val = 0;
	    
	    if (*fmt == 'd' && 1 == sscanf(val, "%d", &vv)) {
		got_val = 1;
		while (isdigit(*val)) {
		    val++;
		}
	    } else if (*fmt == 'o' && 1 == sscanf(val, "%o", &vv)) {
		/* XXX: completely untested */
		got_val = 1;
		while (*val >= '0' && *val <= '7') {
		    val++;
		}
	    } else if (*fmt == 'b') {
		/* XXX: completely untested */
		while (*val == '0' || *val == '1') {
		    got_val = 1;
		    vv = (vv << 1) | (*val - '0');
		    val++;
		}
	    }
	    
	    /* XXX: tell more about unscanable strings !!! */
	    if (! got_val) {
		break;
	    }
	    
	    /* now put pfx-num bytes to the output buffer: */
	    while (pfx > 0) {
		int c1, c2;
		
		v = vv >> ((pfx - 1) * 8);
		
		if (idx + 100 >= ret_len) {
		    ret = ckrealloc(ret, ret_len += 100);
		}
		
		c1 = (v & 0xf0) >> 4;
		c2 = v & 0x0f;
		if ((c1 += '0') > '9') c1 += 7;
		if ((c2 += '0') > '9') c2 += 7;
		
		ret [idx++] = c1, ret [idx++] = c2;
		if (*val) {
		    ret [idx++] = ':';
		}
		pfx--;
	    }
	    
	    fmt++;
	} else if (*fmt == 'x') {
	    /* a simple one: copy the following hex-digits: */
	    while (pfx > 0) {		
		if (! *val || ! val [1]) {
		    break;
		}
		if (idx + 100 >= ret_len) {
		    ret = ckrealloc(ret, ret_len += 100);
		}
		ret [idx++] = *val++;
		ret [idx++] = *val++;
		if (*val == ':') {
		    val++, ret [idx++] = ':';
		}
		pfx--;
	    }
	    fmt++;
	} else {
	    fprintf(stderr, "scotty: unknown textual-format `%c'\n",
		    *fmt); 
	    fmt++;
	}
	
	/* look about a separator: */
	if (*fmt && ! isdigit(*fmt) && *fmt != '*') {
	    if (have_pfx && *val) {
		val++;
	    }
	    fmt++;
	}
	
	/* repeat with last format, if datas avail: */
	if (! *fmt && *val) {
	    fmt = last_fmt;
	}
    }
    
    ret [idx] = 0;
    return ret;
}

/*
 *----------------------------------------------------------------------
 *
 * ScanIntTC --
 *
 *	This procedure scans a string unsing the textual convention and
 *	returns the integer representation of the value. Parts marked 
 *	XXX are untested.
 *
 * Results:
 *	The pointer to a static string containing the formatted value
 *	or NULL if there is no such MIB node.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static char*
ScanIntTC(val, fmt)
    char *val;
    char *fmt;
{
    static char ret [100];
    int dpt = -1;				/* decimal point */
    char format;

    /* 
     * Sanity check and simple case: 
     */

    if (! fmt) {
        return NULL;
    } else {
        format = *fmt;
    }

    if (*fmt == 'd' && ! fmt [1]) {
        return val;
    }

    /* 
     * Must be one of these:
     */

    if (*fmt != 'd' && *fmt != 'o' && *fmt != 'b') {
        return NULL;
    }
    
    /* `d' format may have the form: d-[0-9]+ */
    if (*fmt == 'd' && fmt [1] == '-' 
	&& fmt [2] >= '0' && fmt [2] < '9') {
        format = *fmt;
	dpt = 0;
	for (fmt += 2; *fmt >= '0' && *fmt <= '9'; fmt++) {
	    dpt = dpt * 10 + *fmt - '0';
	}
    } else if (fmt [1]) {
        return NULL;					/* wrong */
    }

    if (dpt >= 0) {

        int idx = 0, do_copy = 0;

	for (; *val; val++) {
	    if (*val == '.') {
	        continue;
	    } else if (*val != '0' || do_copy) {
	        ret [idx++] = *val;
	    } else if (*val != '0' && ! do_copy) {
	        do_copy = 1;
	    }
	}

	ret [idx] = 0;
	return ret;

    } else if (format == 'o') {

        int vv;
	if (1 == sscanf(val, "%o", &vv)) {
	    /* XXX: completely untested */
	    sprintf(ret, "%d", vv);
	    return ret;
	}
    } else if (format == 'b') {

        int vv = 0, got_val = 0;
	
	/* XXX: completely untested */
	while (*val == '0' || *val == '1') {
	    got_val = 1;
	    vv = (vv << 1) | (*val - '0');
	    val++;
	}
	  
	if (got_val) {
	    sprintf(ret, "%d", vv);
	    return ret;
	}
    }

    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * ScanTimeTicks --
 *
 *	This procedure scans a string representing a TimeTicks value 
 *	returns the unsigned number in deci-seconds.
 *
 * Results:
 *	The pointer to a static string containing the formatted value
 *	or NULL if there is no such MIB node.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static char*
ScanTimeTicks(value)
    char *value;
{
    u_int u, d, h, m, s, f, n;
    static char str[20];

    n = sscanf(value, "%dd %d:%d:%d.%d", &d, &h, &m, &s, &f);
    if (n == 5) {
	u = d * 8640000 + h * 360000 + m * 6000 + s * 100 + f;
	goto done;
    }
    n = sscanf(value, "%d:%d:%d.%d", &h, &m, &s, &f);
    if (n == 4) {
	u = h * 360000 + m * 6000 + s * 100 + f;
	goto done;
    }
    n = sscanf(value, "%d:%d:%d", &h, &m, &s);
    if (n == 3) {
	u = h * 360000 + m * 6000 + s * 100;
	goto done;
    }

    u = 0;
    while (isdigit(*value)) {
	u = 10 * u + *value - '0';
	value++;
    }

done:
    FormatUnsigned(u, str);
    return str;
}

/*
 *----------------------------------------------------------------------
 *
 * TnmMibFormatValue --
 *
 *	This procedure converts a value from its base representation
 *	into the format defined by textual conventions or enumerations.
 *
 * Results:
 *	The pointer to a static string containing the formatted value
 *	or the original value if no conversion applies.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

char *
TnmMibFormatValue(typePtr, syntax, value)
    TnmMibType *typePtr;
    int syntax;
    char *value;
{
    if (typePtr) {
	
	/*
	 * Check if we have an enumeration for this value. Otherwise,
	 * try to apply a display hint, if we have one.
	 */
	
	TnmMibEnum *ePtr;
	int ival = atoi(value);
	for (ePtr = typePtr->enumList; ePtr; ePtr = ePtr->nextPtr) {
	    if (ePtr->value == ival) {
		return ePtr->label;
	    }
	}
	
	if (typePtr->displayHint) {
	    char *result = NULL;
	    switch (syntax) {
	    case ASN1_OCTET_STRING:
		result = FormatOctetTC(value, typePtr->displayHint);
		break;
	    case ASN1_INTEGER:
		result = FormatIntTC(value, typePtr->displayHint);
		break;
	    }
	    if (result) {
		return result;
	    }
	}
    }

    /*
     * Finally, apply some special conversions for some well-known
     * SNMP base types.
     */

    switch (syntax) {
    case ASN1_TIMETICKS:
	value = FormatTimeTicks(value);
	break;
    case ASN1_OBJECT_IDENTIFIER:
	value = FormatOID(value);
	break;
    }

    return value;
}

/*
 *----------------------------------------------------------------------
 *
 * TnmMibFormat --
 *
 *	This procedure converts a value from its base representation
 *	into the format defined by textual conventions or enumerations.
 *
 * Results:
 *	The pointer to a static string containing the formatted value
 *	or NULL if there is no such MIB node.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

char*
TnmMibFormat(name, exact, value)
    char *name;
    int exact;
    char *value;
{
    TnmMibNode *nodePtr = TnmMibFindNode(name, NULL, exact);
   
    if (nodePtr) {
	return TnmMibFormatValue(nodePtr->typePtr, nodePtr->syntax, value);
    }

    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * TnmMibScanValue --
 *
 *	This procedure converts a value into its base representation
 *	by applying textual conventions or by converting enumerations
 *	into simple numbers.
 *
 * Results:
 *	The pointer to a static string containing the scanned value.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

char*
TnmMibScanValue(typePtr, syntax, value)
    TnmMibType *typePtr;
    int syntax;
    char *value;
{
    if (typePtr) {
	
	/*
	 * Check if we have an enumeration for this value. Otherwise,
	 * try to apply a display hint.
	 */
	
	TnmMibEnum *ePtr;
	for (ePtr = typePtr->enumList; ePtr; ePtr = ePtr->nextPtr) {
	    if (strcmp(ePtr->label, value) == 0) {
		static char buf[20];
		sprintf(buf, "%d", ePtr->value);
		return buf;
	    }
	}
	
	if (typePtr->displayHint) {
	    char *result = NULL;
	    switch (syntax) {
	    case ASN1_OCTET_STRING:
		result = ScanOctetTC(value, typePtr->displayHint);
		break;
	    case ASN1_INTEGER:
		result = ScanIntTC(value, typePtr->displayHint);
		break;
	    }
	    if (result) {
		return result;
	    }
	}
    }

    switch (syntax) {
    case ASN1_TIMETICKS:
	value = ScanTimeTicks(value);
	break;
    case ASN1_OBJECT_IDENTIFIER:
	value = TnmMibGetOid(value);
	break;
    }

    return value;
}

/*
 *----------------------------------------------------------------------
 *
 * TnmMibScan --
 *
 *	This procedure converts a value into its base representation
 *	by applying textual conventions or by converting enumerations
 *	into simple numbers.
 *
 * Results:
 *	The pointer to a static string containing the scanned value
 *	or NULL if there is no such MIB node.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

char*
TnmMibScan(name, exact, value)
    char *name;
    int exact;
    char *value;
{
    TnmMibNode *nodePtr = TnmMibFindNode(name, NULL, exact);
    
    if (nodePtr) {
	return TnmMibScanValue(nodePtr->typePtr, nodePtr->syntax, value);
    }

    return NULL;
}
