/*
 * METADATA - Common set of routines for handling data according to
 *	      flexible definitions.
 *
 * Author:
 * Emile van Bergen, emile@evbergen.xs4all.nl
 *
 * Permission to redistribute an original or modified version of this program
 * in source, intermediate or object code form is hereby granted exclusively
 * under the terms of the GNU General Public License, version 2. Please see the
 * file COPYING for details, or refer to http://www.gnu.org/copyleft/gpl.html.
 *
 * History:
 * 2000/12/12 - EvB - Created
 * 2000/12/27 - EvB - Added the meta_add_ functions
 *                    Added the META_VND type
 * 2001/01/18 - EvB - Added the out_ fields in the fixed field and attribute
 *                    types
 * 2001/02/06 - EvB - Removed them again - will first write the thing
 *                    without the whole canonicalisation of A/V list idea.
 * 2001/02/11 - EvB - Removed separate tag meta definitions; will use separate
 *                    types instead.
 *                  - Renamed all members to according to how they're written
 *                    in the dictionary.
 * 2001/03/17 - EvB - Unified the attribute and fixed field types
 *                  - Changed numeric references to pointers for speed
 *                  - Moved all higher-level things, including a dictionary
 *                    reader and encoding / decoding functions to another
 *                    file.
 * 2001/04/17 - EvB - Added META_VAL type, to define names for numeric values
 * 2001/04/24 - EvB - Changed behaviour of dictionary items with vnd = ANY;
 * 		      they won't match any query
 * 		    - Added a 'single' space qualifier that says a space
 * 		      can only hold one attribute. Good for standard VSAs etc.
 * 		    - Changed the single encapsulating item for a space into
 * 		      a linked list of alternatives (can be searched through
 * 		      based on vendor nr). Space owns the items on the list.
 * 		    - Made meta_add_item handle the addition to a space's
 * 		      encapsulating item list itself.
 * 2001/07/10 - EvB - Added meta_getitembyspec here as we need that in more 
 * 		      places than just the compiler.
 * 2001/09/13 - EvB - Added nodec and noenc item options that affect
 *		      meta_decode (only follow subspaces, ignore data) and 
 *		      meta_buildtree/meta_encode (ignore), resp. This
 *		      obsoletes the MT_IGNORE data type.
 * 2002/12/12 - EvB - Fixed incorrect behaviour of meta_getitemby*, which used
 * 		      META_ORD_ERR as a wildcard for vendor numbers; introduced
 * 		      META_VND_WILD for that purpose instead.
 * 2005/06/15 - EvB - Added 'max_size' to specify maximum value size before
 * 		      splitting (used by buildtree); calculated by additem
 */

char metadata_id[] = "METADATA - Copyright (C) 2000 Emile van Bergen.";


/*
 * INCLUDES & DEFINES
 */


#include <stdlib.h>	/* For malloc() / free() */
#include <string.h>	/* For strncpy() for META_NAME fields */

#include <metadata.h>

#define DEBUGLEVEL 3
#include <debug.h>


/*
 * FUNCTIONS
 */


/*
 * CREATION / DELETION
 */


META *meta_new()
{
	META *ret;

	/* Allocate meta object */
	ret = (META *)malloc(sizeof(META));
	if (!ret) return 0;
	memset(ret, 0, sizeof(META));

	/* Initialise members */
	ret->spaces = 0;
	ret->vendors = 0;

	return ret;
}


void meta_del(META *m)
{
	META_SPC *s, *tmps;
	META_VND *v, *tmpv;
	META_ITEM *i, *tmpi;
	META_VAL *vl, *tmpvl;

	if (!m) return;

	for(s = m->spaces; s; s = tmps) {

		/* Free items in this space */
		for(i = s->items; i; i = tmpi) {

			/* Free this item's values */
			for(vl = i->values; vl; vl = tmpvl) {
				tmpvl = vl->next;
				free(vl);
			}

			/* Free item itself */
			tmpi = i->next;
			free(i);
		}

		/* Free encapsulating items for this space; these are COPIES 
		   of the items on another space's normal items list! 
		   This make a separate list possible without an extra 
		   struct around the items. */
		for(i = s->enc_items; i; i = tmpi) {
			tmpi = i->next;
			free(i);
		}

		/* Free space itself */
		tmps = s->next;
		free(s);
	}

	/* Free vendors */
	for(v = m->vendors; v; v = tmpv) {
		tmpv = v->next;
		free(v);
	}

	/* Free metadata object itself */
	free(m);
}


/*
 * LOW LEVEL UTILITY FUNCIONS
 */


/* Get ordinal from an arbitrarily aligned data fragment in network order.
   WARNING: a zero size gives a zero value. This is really wrong of course, but 
   just a little too convenient in 99% of the cases not to do it. In the other
   1%, it's just as easy to trap on size equals zero before calling as it is on
   result equals error after calling, and I don't care about the size > 8 case,
   sorry. */

META_ORD getord(char *data, int size)
{
	META_ORD ret;

	ret = 0;

	/* Not as strange as Duff's device */
	switch(size) {
	    case 8:  ret |= *(unsigned char *)data++;
	    case 7:  ret <<= 8; ret |= *(unsigned char *)data++;
	    case 6:  ret <<= 8; ret |= *(unsigned char *)data++;
	    case 5:  ret <<= 8; ret |= *(unsigned char *)data++;
	    case 4:  ret <<= 8; ret |= *(unsigned char *)data++;
	    case 3:  ret <<= 8; ret |= *(unsigned char *)data++;
	    case 2:  ret <<= 8; ret |= *(unsigned char *)data++;
	    case 1:  ret <<= 8; ret |= *(unsigned char *)data++;
	}
	return ret;
}


/* Put ordinal at an arbitrarily aligned data fragment in network order */

void putord(char *data, int size, META_ORD ord)
{
	switch(size) {
	    case 8: data[7] = ord & 0xff; ord >>= 8;
	    case 7: data[6] = ord & 0xff; ord >>= 8;
	    case 6: data[5] = ord & 0xff; ord >>= 8;
	    case 5: data[4] = ord & 0xff; ord >>= 8;
	    case 4: data[3] = ord & 0xff; ord >>= 8;
	    case 3: data[2] = ord & 0xff; ord >>= 8;
	    case 2: data[1] = ord & 0xff; ord >>= 8;
	    case 1: data[0] = ord & 0xff; ord >>= 8;
	}
}


/* This function initializes a META_NAME pointed to by 'name' with the
   string at 's', while doing proper bounds checking and zero termination. */

void setname(char *name, char *s)
{
	strncpy(name, s, sizeof(META_NAME) - 1);
	name[sizeof(META_NAME) - 1] = 0;
}

void setname_n(char *name, char *s, int slen)
{
	if (slen > sizeof(META_NAME) - 1) slen = sizeof(META_NAME) - 1;
	strncpy(name, s, slen);
	name[slen] = 0;
}

void setspec_n(char *spec, char *s, int slen)
{
	if (slen > sizeof(META_SPEC) - 1) slen = sizeof(META_SPEC) - 1;
	strncpy(spec, s, slen);
	spec[slen] = 0;
}


/*
 * HANDLING SPACES
 */


/* Add a space */

META_SPC *meta_addspc(META *m, META_ORD nr, char *name,
                char atr_ofs, char atr_size,
                char vnd_ofs, char vnd_size,
                char single)
{
	META_SPC *ret;

	/* Allocate a space object */
	ret = (META_SPC *)malloc(sizeof(META_SPC));
	if (!ret) return 0;
	memset(ret, 0, sizeof(META_SPC));

	/* Initialize members */
	ret->nr = nr;
	setname(ret->name, name);

	ret->atr_ofs = atr_ofs; ret->atr_size = atr_size;
	ret->vnd_ofs = vnd_ofs; ret->vnd_size = vnd_size;
	ret->single = single; ret->enc_items = 0; ret->items = 0;

	/* Add the space to the space list */
	ret->next = m->spaces;
	m->spaces = ret;

	return ret;
}


/* Find a space by its number */

META_SPC *meta_getspcbynr(META *m, META_ORD nr)
{
	META_SPC *ret;

	for(ret = m->spaces; ret && ret->nr != nr; ret = ret->next);
	return ret;
}


/* Find a space by its name */

META_SPC *meta_getspcbyname(META *m, char *name)
{
	META_SPC *ret;

	for(ret = m->spaces; 
	    ret && strcmp(ret->name, name) != 0;
	    ret = ret->next);
	return ret;
}


/*
 * HANDLING VENDORS
 */


/* Add a vendor */

META_VND *meta_addvnd(META *m, META_ORD nr, char *name)
{
	META_VND *ret;

	/* Allocate a vendor object */
	ret = (META_VND *)malloc(sizeof(META_VND));
	if (!ret) return 0;
	memset(ret, 0, sizeof(META_VND));

	/* Initialise members */
	ret->nr = nr;
	setname(ret->name, name);

	/* Add the vendor to the vendor list */
	ret->next = m->vendors;
	m->vendors = ret;

	return ret;
}


/* Find a vendor by its number */

META_VND *meta_getvndbynr(META *m, META_ORD nr)
{
	META_VND *ret;

	for(ret = m->vendors; ret && ret->nr != nr; ret = ret->next); 
	return ret;
}


/* Find a vendor by its name */

META_VND *meta_getvndbyname(META *m, char *name)
{
	META_VND *ret;

	for(ret = m->vendors; 
	    ret && strcmp(ret->name, name) != 0;
	    ret = ret->next);
	return ret;
}


/*
 * HANDLING DATA ITEMS
 */


/* Add an item */

META_ITEM *meta_additem(META *m, META_SPC *s,
                META_ORD nr, META_ORD vnd, char *name,
                char len_ofs, char len_size, char len_adj,
                char val_ofs, char val_size, char val_type,
                META_SPC *subspace, char nodec, char noenc,
		char keeprej, char keepacct)
{
	META_ITEM *ret, *tmp;

	/* Allocate an item object */
	ret = (META_ITEM *)malloc(sizeof(META_ITEM));
	if (!ret) return 0;
	memset(ret, 0, sizeof(META_ITEM));

	/* Initialise members */
	ret->nr = nr;
	ret->vnd = vnd;
	setname(ret->name, name);
	ret->len_ofs = len_ofs; ret->len_size = len_size; 
	ret->len_adj = len_adj;
	ret->val_ofs = val_ofs; ret->val_size = val_size; 
	ret->val_type = val_type;
	ret->spc = s;
	ret->subspace = subspace;
	ret->nodec = nodec; ret->noenc = noenc;
	ret->keeprej = keeprej; ret->keepacct = keepacct;

	/* Calculate max_size if not given as parameter (optional param tbd) */
	ret->max_size = val_size;
	if (val_size <= 0)
	      ret->max_size = (1 << (len_size << 3)) - 1 + len_adj + val_size;

	/* Add to list */
	ret->next = s->items;
	s->items = ret;

	/* See if this item has a subspace */
	if (subspace) {

		/* Yes, so create copy of ourself */
		tmp = (META_ITEM *)malloc(sizeof(META_ITEM));
		if (!tmp) { free(ret); return 0; }
		memcpy(tmp, ret, sizeof(META_ITEM));

		/* And add the copy to our subspace's encapsulating item list */
		tmp->next = subspace->enc_items;
		subspace->enc_items = tmp;
	}

	return ret;
}


/* Find an item by its number, given a space and vendor number.
   A vendor number of META_VND_WILD means any vendor number. */

META_ITEM *meta_getitembynr(META *m, META_SPC *s, META_ORD nr, META_ORD vnd)
{
	META_ITEM *ret;

	ret = 0;
	if (s) {
		if (vnd != META_VND_WILD) {

			for(ret = s->items;
			    ret && (ret->nr != nr || ret->vnd != vnd);
			    ret = ret->next);
			return ret;
		}

		for(ret = s->items;
		    ret && ret->nr != nr;
		    ret = ret->next);

		return ret;
	}

	for(s = m->spaces; 
	    s && !(ret = meta_getitembynr(m, s, nr, vnd));
	    s = s->next);
	return ret;
}


/* Find an item by its name in a space, or in all spaces if s == 0.
   A vendor number of META_VND_WILD means any vendor number. */

META_ITEM *meta_getitembyname(META *m, META_SPC *s, char *name, META_ORD vnd)
{
	META_ITEM *ret;

	ret = 0;
	if (s) {
		if (vnd != META_VND_WILD) {
			for(ret = s->items;
			    ret && (strcmp(ret->name, name) || ret->vnd != vnd);
			    ret = ret->next);
			return ret;
		}
		for(ret = s->items;
		    ret && strcmp(ret->name, name);
		    ret = ret->next);
		return ret;
	}

	for(s = m->spaces; 
	    s && !(ret = meta_getitembyname(m, s, name, vnd));
	    s = s->next);
	return ret;
}


/* Find an item by ASCII specification, as in '[SPACE:[Vendor:]]Name' */

META_ITEM *meta_getitembyspec(META *m, char *spec)
{
	META_NAME tmpname;
	META_SPC *s;
	META_VND *v;
	META_ORD vnd;
	char *c;

	/* Set defaults: all spaces, any vendor */
	s = 0; vnd = META_VND_WILD;

	/* Check if we have a colon */
	c = strchr(spec, ':'); 
	if (c) {
		/* Yes, so find space by the string before the colon */
		memcpy(tmpname, spec, c - spec); tmpname[c - spec] = 0;
		s = meta_getspcbyname(m, tmpname); if (!s) return 0;
		spec = c + 1;

		/* Check if we have another colon in the remainder */
		c = strchr(spec, ':');
		if (c) {
			/* Yes, so find vendor by what's before it */
			memcpy(tmpname, spec, c - spec); tmpname[c - spec] = 0;
			v = meta_getvndbyname(m, tmpname); if (!v) return 0;
			vnd = v->nr; spec = c + 1;
		}
	}

	/* Search in the usual way */
	return meta_getitembyname(m, s, spec, vnd);
}


/*
 * HANDLING VALUES 
 */


/* Add a named constant value for a numeric item */

META_VAL *meta_addval(META *m, META_ITEM *i, META_ORD nr, char *name)
{
	META_VAL *ret;

	/* Allocate a value object */
	ret = (META_VAL *)malloc(sizeof(META_VAL));
	if (!ret) return 0;
	memset(ret, 0, sizeof(META_VAL));

	/* Initialise members */
	ret->nr = nr;
	setname(ret->name, name);

	/* Add the value to this item's value list */
	ret->next = i->values;
	i->values = ret;

	return ret;
}


/* Find a named constant by its value */
 
META_VAL *meta_getvalbynr(META *m, META_ITEM *i, META_ORD nr)
{
	META_VAL *ret;

	for(ret = i->values; ret && ret->nr != nr; ret = ret->next);
	return ret;
}


/* Find a constant value by its name */
 
META_VAL *meta_getvalbyname(META *m, META_ITEM *i, char *name)
{
	META_VAL *ret;

	for(ret = i->values; 
	    ret && strcmp(ret->name, name) != 0;
	    ret = ret->next);
	return ret;
}



syntax highlighted by Code2HTML, v. 0.9.1