/*
 * tnmMibFrozen.c --
 *
 *	Save and load MIB-Definitions in/from a frozen-format file.
 *
 * 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"

/*
 * Strings are collected in a hashtable for every parsed mib. This allows
 * us to write all strings in one big chunk so that we do not need to
 * allocate every single string. The offset (== size) of the string pool
 * is maintained in poolOffset.
 */

static Tcl_HashTable *poolHashTable = NULL;
static int poolOffset = 0;

#define IDY_MAGIC	"Nase"

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

static void
PoolInit		_ANSI_ARGS_((void));

static void
PoolDelete		_ANSI_ARGS_((void));

static void
PoolAddString		_ANSI_ARGS_((char *string));

static int
PoolGetOffset		_ANSI_ARGS_((char *string));

static void
PoolSave		_ANSI_ARGS_((FILE *fp));

static void
SaveEnum		_ANSI_ARGS_((TnmMibEnum *enumPtr, FILE *fp));

static void
SaveType		_ANSI_ARGS_((TnmMibType *typePtr, int *i, FILE *fp));

static void
SaveNode		_ANSI_ARGS_((TnmMibNode *nodePtr, int *i, FILE *fp));

static void
CollectData		_ANSI_ARGS_((int *numEnums, int *numTcs,
				     int *numNodes, TnmMibNode *nodePtr));
static void
SaveData		_ANSI_ARGS_((FILE *fp, int numEnums, int numTcs, 
				     int numNodes, TnmMibNode *nodePtr));


/*
 *----------------------------------------------------------------------
 *
 * PoolInit --
 *
 *	This procedure initializes the hash table that is used to
 *	create a string pool. The pool is used to eliminate 
 *	duplicated strings.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
PoolInit()
{
    poolOffset = 0;
    if (poolHashTable == NULL) {
	poolHashTable = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
    }
    Tcl_InitHashTable(poolHashTable, TCL_STRING_KEYS);
}

/*
 *----------------------------------------------------------------------
 *
 * PoolDelete --
 *
 *	This procedure clears the memory used to create the string
 *	pool. Very simple and really not worth the comments. Thats
 *	why this one is getting a bit longer as usual. :-)
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
PoolDelete()
{
    if (poolHashTable) {
        Tcl_DeleteHashTable(poolHashTable);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * PoolAddString --
 *
 *	This procedure adds a string to the pool if it is not yet 
 *	there. The value is initialized to mark this entry as used.
 *	The total offset is incremented to get total memory required 
 *	for the string pool. 
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
PoolAddString(s)
    char *s;
{
    Tcl_HashEntry *entryPtr;
    int isnew;

    if (! s) return;

    entryPtr = Tcl_CreateHashEntry(poolHashTable, s, &isnew);
    if (! isnew) {
	return;
    }
    Tcl_SetHashValue(entryPtr, 1);
    poolOffset += strlen(s) + 1;
}

/*
 *----------------------------------------------------------------------
 *
 * PoolGetOffset --
 *
 *	This procedure returns the offset to the given string in the 
 *	string pool or 0 if the string is not in the pool.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
PoolGetOffset(s)
    char *s;
{
    Tcl_HashEntry *entryPtr;

    if (! s) return 0;

    entryPtr = Tcl_FindHashEntry(poolHashTable, s);
    if (entryPtr) {
        return (int) Tcl_GetHashValue(entryPtr);
    } else {
	return 0;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * PoolSave --
 *
 *	This procedure writes the string pool to the given file pointer.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
PoolSave(fp)
    FILE *fp;
{
    Tcl_HashSearch searchPtr;
    Tcl_HashEntry *entryPtr;

    /*
     * Save magic and size:
     */

    poolOffset += strlen(IDY_MAGIC) + 1;
    fwrite((char *) &poolOffset, sizeof(int), 1, fp);
    fwrite(IDY_MAGIC, 1, strlen(IDY_MAGIC) + 1, fp);

    /*
     * Save the strings in the pool:
     */

    poolOffset = strlen(IDY_MAGIC) + 1;
    entryPtr = Tcl_FirstHashEntry(poolHashTable, &searchPtr);
    while (entryPtr) {
	char *s = Tcl_GetHashKey(poolHashTable, entryPtr);
	int len = strlen(s) + 1;
	Tcl_SetHashValue(entryPtr, poolOffset);
	fwrite(s, 1, len, fp);
	poolOffset += len;
	entryPtr = Tcl_NextHashEntry(&searchPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * CollectData --
 *
 *	This procedure collects the strings (by adding them to the pool)
 *	and counts the number of enums, tcs and nodes to be saved.
 *
 * Results:
 *	Returns the number of enums, tcs and nodes.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
CollectData(numEnums, numTcs, numNodes, nodePtr)
    int *numEnums, *numTcs, *numNodes;
    TnmMibNode *nodePtr;
{
    TnmMibNode *ptr;
    TnmMibEnum *enumPtr;
    TnmMibType *typePtr;

    *numEnums = *numTcs = *numNodes = 0;
    for (ptr = nodePtr; ptr; (*numNodes)++, ptr = ptr->nextPtr) {
	PoolAddString(ptr->label);
	PoolAddString(ptr->parentName);
	PoolAddString(ptr->fileName);
	PoolAddString(ptr->moduleName);
	PoolAddString(ptr->index);
	if (ptr->typePtr) {
	    (*numTcs)++;
	    PoolAddString(ptr->typePtr->name);
	    PoolAddString(ptr->typePtr->fileName);
	    PoolAddString(ptr->typePtr->moduleName);
	    PoolAddString(ptr->typePtr->displayHint);
	    for (enumPtr = ptr->typePtr->enumList; 
		 enumPtr; (*numEnums)++, enumPtr = enumPtr->nextPtr) {
		PoolAddString(enumPtr->label);
	    }
	}
    }
    for (typePtr = tnmMibTypeList; 
	 typePtr != tnmMibTypeSaveMark; typePtr = typePtr->nextPtr) {
        (*numTcs)++;
	PoolAddString(typePtr->name);
	PoolAddString(typePtr->fileName);
	PoolAddString(typePtr->moduleName);
	PoolAddString(typePtr->displayHint);
	for (enumPtr = typePtr->enumList; 
	     enumPtr; (*numEnums)++, enumPtr = enumPtr->nextPtr) {
	    PoolAddString(enumPtr->label);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * SaveEnum --
 *
 *	This procedure writes a TnmMibEnum structure. The char*
 *	pointers are used to store the offset into the string pool.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
SaveEnum(enumPtr, fp)
    TnmMibEnum *enumPtr;
    FILE *fp;
{
    TnmMibEnum en;

    memcpy((char *) &en, (char *) enumPtr, sizeof(TnmMibEnum));
    en.label = (char *) PoolGetOffset(enumPtr->label);
    en.nextPtr = (TnmMibEnum *) (enumPtr->nextPtr ? 1 : 0);

    fwrite((char *) &en, sizeof(en), 1, fp);
}

/*
 *----------------------------------------------------------------------
 *
 * SaveType --
 *
 *	This procedure writes a TnmMibType structure. The char*
 *	pointers are used to store the offset into the string pool.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
SaveType(typePtr, i, fp)
    TnmMibType *typePtr;
    int *i;
    FILE *fp;
{
    TnmMibType tc;

    memcpy((char *) &tc, (char *) typePtr, sizeof(TnmMibType));
    tc.name = (char *) PoolGetOffset(typePtr->name);
    tc.fileName = (char *) PoolGetOffset(typePtr->fileName);
    tc.moduleName = (char *) PoolGetOffset(typePtr->moduleName);
    tc.displayHint = (char *) PoolGetOffset(typePtr->displayHint);
    if (typePtr->enumList) {
	TnmMibEnum *e;
	tc.enumList = (TnmMibEnum *) (*i + 1);
	for (e = typePtr->enumList; e; (*i)++, e = e->nextPtr) ;
    }
    tc.nextPtr = (TnmMibType *) (typePtr->nextPtr ? 1 : 0);

    fwrite((char *) &tc, sizeof(TnmMibType), 1, fp);
}

/*
 *----------------------------------------------------------------------
 *
 * SaveNode --
 *
 *	This procedure writes a TnmMibNode structure. The char*
 *	pointers are used to store the offset into the string pool.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
SaveNode(nodePtr, i, fp)
    TnmMibNode *nodePtr;
    int *i;
    FILE *fp;
{
    struct TnmMibNode no;

    memcpy((char *) &no, (char *) nodePtr, sizeof(TnmMibNode));
    no.label = (char *) PoolGetOffset(nodePtr->label);
    no.parentName = (char *) PoolGetOffset(nodePtr->parentName);
    no.fileName = (char *) PoolGetOffset(nodePtr->fileName);
    no.moduleName = (char *) PoolGetOffset(nodePtr->moduleName);
    no.index = (char *) PoolGetOffset(nodePtr->index);
    no.childPtr = 0;
    if (nodePtr->typePtr) {
	no.typePtr = (TnmMibType *) ++(*i);
    }
    no.nextPtr = (TnmMibNode *) (nodePtr->nextPtr ? 1 : 0);
    
    fwrite((char *) &no, sizeof(TnmMibNode), 1, fp);
}

/*
 *----------------------------------------------------------------------
 *
 * SaveData --
 *
 *	This procedure writes the node list as an index file to fp.
 *
 *	int (#enum elements)
 *  		followed by #enum structs
 * 	int (#tc elements)
 *		followed by #tc structs
 *      int (#node elements)
 *		followed by n TnmMibNode structs.
 *
 *	string pointer are saved as offsets relative to the pool,
 *	enum pointer are saved as offset to the saved enums (+ 1)
 *	tc pointer dito.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
SaveData(fp, numEnums, numTCs, numNodes, nodePtr)
    FILE *fp;
    int numEnums, numTCs, numNodes;
    TnmMibNode *nodePtr;
{
    TnmMibNode *nPtr;
    TnmMibEnum *ePtr;
    TnmMibType *tPtr;
    int i;
    
    /*
     * Save numEnums TnmMibEnum structures:
     */

    fwrite((char *) &numEnums, sizeof(int), 1, fp);
    for (nPtr = nodePtr; nPtr; nPtr = nPtr->nextPtr) {
	if (nPtr->typePtr) {
	    for (ePtr = nPtr->typePtr->enumList; ePtr; ePtr = ePtr->nextPtr) {
		SaveEnum(ePtr, fp);
	    }
	}
    }
    for (tPtr=tnmMibTypeList; tPtr != tnmMibTypeSaveMark; tPtr = tPtr->nextPtr) {
	for (ePtr = tPtr->enumList; ePtr; ePtr = ePtr->nextPtr) {
	    SaveEnum(ePtr, fp);
	}
    }

    /*
     * Save numTCs Tnm_MibTC structures:
     */

    fwrite((char *) &numTCs, sizeof(int), 1, fp);
    for (i = 0, nPtr = nodePtr; nPtr; nPtr = nPtr->nextPtr) {
	if (nPtr->typePtr) {
	    SaveType(nPtr->typePtr, &i, fp);
	}
    }
    for (tPtr=tnmMibTypeList; tPtr != tnmMibTypeSaveMark; tPtr = tPtr->nextPtr) {
	SaveType(tPtr, &i, fp);
    }
    
    /*
     * Save numNodes TnmMibNode structures:
     */

    fwrite((char *) &numNodes, sizeof(int), 1, fp);
    for (i = 0, nPtr = nodePtr; nPtr; nPtr = nPtr->nextPtr) {
	SaveNode(nPtr, &i, fp);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TnmMibWriteFrozen --
 *
 *	This procedure writes a frozen MIB file. See the description
 *	of TnmMibReadFrozen() for an explanation of the format.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TnmMibWriteFrozen(fp, nodePtr)
    FILE *fp;
    TnmMibNode *nodePtr;
{
    int numEnums, numTcs, numNodes; 
    PoolInit();
    CollectData(&numEnums, &numTcs, &numNodes, nodePtr);
    PoolSave(fp);
    SaveData(fp, numEnums, numTcs, numNodes, nodePtr);
    PoolDelete();
}

/*
 *----------------------------------------------------------------------
 *
 * TnmMibReadFrozen --
 *
 *	This procedure reads a frozen MIB file that was written by 
 *	TnmMibWriteFrozen(). The expected format is:
 *
 *	int (stringpool size)
 *		pool_size bytes
 *	int (number of enum structs)
 *		#enum structs
 *	int (number of textual conventions)
 *		#tc structs
 *	int (number of nodes)
 *		#node structs
 *
 * Results:
 *	A pointer to the MIB root node or NULL if there was an error.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

TnmMibNode*
TnmMibReadFrozen(fp)
    FILE *fp;
{
    TnmMibNode *root = NULL;
    int poolSize;
    char *pool;
    int numEnums;
    TnmMibEnum *enums = NULL;
    int numTcs;
    TnmMibType *tcs = NULL;
    int numNodes;
    TnmMibNode *nodes;

    /*
     * First, read the string space: 
     */

    if (1 != fread((char *) &poolSize, sizeof(int), 1, fp)) {
	TnmWriteLogMessage(TNM_LOG_DEBUG,
			   "error reading string pool size...\n");
	return NULL;
    }

    pool = ckalloc(poolSize);
    if (poolSize != fread(pool, 1, poolSize, fp)) {
	TnmWriteLogMessage(TNM_LOG_DEBUG,
			   "error reading string pool...\n");
	return NULL;
    }

    /*
     * Next, read the enumerations:
     */

    if (1 != fread((char *) &numEnums, sizeof(int), 1, fp)) {
	TnmWriteLogMessage(TNM_LOG_DEBUG,
			   "error reading enum counter...\n");
	return NULL;
    }

    if (numEnums > 0) {
	TnmMibEnum *enumPtr;
	int i;
	enums = (TnmMibEnum *) ckalloc(numEnums * sizeof(TnmMibEnum));
	if (numEnums != fread(enums, sizeof(TnmMibEnum), numEnums, fp)) {
	    TnmWriteLogMessage(TNM_LOG_DEBUG,
			       "error reading enums...\n");
	    ckfree((char *) enums);
	    return NULL;
	}
    
	/* 
	 * Adjust the string and chain pointers:
	 */
	
	for (i = 0, enumPtr = enums; i < numEnums; i++, enumPtr++) {
	    enumPtr->label = (int) enumPtr->label + pool;
	    enumPtr->nextPtr = enumPtr->nextPtr ? enumPtr + 1 : 0;
	}
    }

    /*
     * Next, read the textual conventions: 
     */

    if (1 != fread((char *) &numTcs, sizeof(int), 1, fp)) {
	TnmWriteLogMessage(TNM_LOG_DEBUG,
			   "error reading tc counter...\n");
	return NULL;
    }
    if (numTcs > 0) {
	TnmMibType *typePtr;
	int i;

	tcs = (TnmMibType *) ckalloc(numTcs * sizeof(TnmMibType));
	if (numTcs != fread(tcs, sizeof(TnmMibType), numTcs, fp)) {
	    TnmWriteLogMessage(TNM_LOG_DEBUG,
			       "error reading tcs...\n");
	    ckfree((char *) tcs);
	    return NULL;
	}
	
	/* 
	 * Adjust string and enum pointers: 
	 */

	for (i = 0, typePtr = tcs; i < numTcs; i++, typePtr++) {
	    typePtr->name = (int) typePtr->name + pool;
	    if (typePtr->fileName) {
	        typePtr->fileName = (int) typePtr->fileName + pool;
	    }
	    if (typePtr->moduleName) {
	        typePtr->moduleName = (int) typePtr->moduleName + pool;
	    }
	    if (typePtr->displayHint) {
		typePtr->displayHint = (int) typePtr->displayHint + pool;
	    }
	    if (typePtr->enumList) {
		typePtr->enumList = (int) typePtr->enumList + enums - 1;
	    }
	    if (typePtr->name[0] != '_') {
		TnmMibAddType(typePtr);
	    }
	}	
    }

    /*
     * Next, read the MIB nodes:
     */

    if (1 != fread((char *) &numNodes, sizeof(int), 1, fp)) {
	TnmWriteLogMessage(TNM_LOG_DEBUG,
			   "error reading node counter...\n");
	return NULL;
    }

    if (numNodes > 0) {
	TnmMibNode *ptr;
	int i;

	nodes = (TnmMibNode *) ckalloc(numNodes * sizeof(TnmMibNode));
	if (numNodes != fread(nodes, sizeof(TnmMibNode), numNodes, fp)) {
	    TnmWriteLogMessage(TNM_LOG_DEBUG,
			       "error reading nodes...\n");
	    ckfree((char *) nodes);
	    return NULL;
	}
	
	/*
	 * Adjust string and tc pointers:
	 */

	for (i = 0, ptr = nodes; i < numNodes; i++, ptr++) {
	    ptr->label = (int) ptr->label + pool;
	    ptr->parentName = (int) ptr->parentName + pool;
	    if (ptr->fileName) {
	        ptr->fileName = (int) ptr->fileName + pool;
	    }
	    if (ptr->moduleName) {
	        ptr->moduleName = (int) ptr->moduleName + pool;
	    }
	    if (ptr->index) {
	        ptr->index = (int) ptr->index + pool;
	    }
	    if (ptr->typePtr) {
	        ptr->typePtr = (int) ptr->typePtr + tcs - 1;
	    }
	    ptr->nextPtr = ptr->nextPtr ? ptr + 1 : 0;
	}
	root = nodes;
    }
    
    return root;
}
