/*
   Copyright (C) Andrew Tridgell <genstruct@tridgell.net> 2002
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
  automatic marshalling/unmarshalling system for C structures
*/

#include "includes.h"

/* see if a range of memory is all zero. Used to prevent dumping of zero elements */
static int all_zero(const char *ptr, unsigned size)
{
	int i;
	if (!ptr) return 1;
	for (i=0;i<size;i++) {
		if (ptr[i]) return 0;
	}
	return 1;
}

/* encode a buffer of bytes into a escaped string */
static char *encode_bytes(TALLOC_CTX *mem_ctx, const char *ptr, unsigned len)
{
	const char *hexdig = "0123456789abcdef";
	char *ret, *p;
	unsigned i;
	ret = talloc(mem_ctx, len*3 + 1); /* worst case size */
	if (!ret) return NULL;
	for (p=ret,i=0;i<len;i++) {
		if (isalnum(ptr[i]) || isspace(ptr[i]) ||
		    (ispunct(ptr[i]) && !strchr("\\{}", ptr[i]))) {
			*p++ = ptr[i];
		} else {
			unsigned char c = *(unsigned char *)(ptr+i);
			if (c == 0 && all_zero(ptr+i, len-i)) break;
			p[0] = '\\';
			p[1] = hexdig[c>>4];
			p[2] = hexdig[c&0xF];
			p += 3;
		}
	}

	*p = 0;

	return ret;
}

/* decode an escaped string from encode_bytes() into a buffer */
static char *decode_bytes(TALLOC_CTX *mem_ctx, const char *s, unsigned *len) 
{
	char *ret, *p;
	unsigned i;
	int slen = strlen(s) + 1;

	ret = talloc(mem_ctx, slen); /* worst case length */
	if (!ret)
		return NULL;
	memset(ret, 0, slen);

	if (*s == '{') s++;

	for (p=ret,i=0;s[i];i++) {
		if (s[i] == '}') {
			break;
		} else if (s[i] == '\\') {
			unsigned v;
			if (sscanf(&s[i+1], "%02x", &v) != 1 || v > 255) {
				return NULL;
			}
			*(unsigned char *)p = v;
			p++;
			i += 2;
		} else {
			*p++ = s[i];
		}
	}
	*p = 0;

	(*len) = (unsigned)(p - ret);
	
	return ret;
}

/* the add*() functions deal with adding things to a struct
   parse_string */

/* allocate more space if needed */
static int addgen_alloc(TALLOC_CTX *mem_ctx, struct parse_string *p, int n)
{
	if (p->length + n <= p->allocated) return 0;
	p->allocated = p->length + n + 200;
	p->s = talloc_realloc(mem_ctx, p->s, p->allocated);
	if (!p->s) {
		errno = ENOMEM;
		return -1;
	}
	return 0;
}

/* add a character to the buffer */
static int addchar(TALLOC_CTX *mem_ctx, struct parse_string *p, char c)
{
	if (addgen_alloc(mem_ctx, p, 2) != 0) {
		return -1;
	}
	p->s[p->length++] = c;
	p->s[p->length] = 0;
	return 0;
}

/* add a string to the buffer */
int addstr(TALLOC_CTX *mem_ctx, struct parse_string *p, const char *s)
{
	int len = strlen(s);
	if (addgen_alloc(mem_ctx, p, len+1) != 0) {
		return -1;
	}
	memcpy(p->s + p->length, s, len+1);
	p->length += len;
	return 0;
}

/* add a string to the buffer with a tab prefix */
static int addtabbed(TALLOC_CTX *mem_ctx, struct parse_string *p, const char *s, unsigned indent)
{
	int len = strlen(s);
	if (addgen_alloc(mem_ctx, p, indent+len+1) != 0) {
		return -1;
	}
	while (indent--) {
		p->s[p->length++] = '\t';
	}
	memcpy(p->s + p->length, s, len+1);
	p->length += len;
	return 0;
}

/* note! this can only be used for results up to 60 chars wide! */
int addshort(TALLOC_CTX *mem_ctx, struct parse_string *p, const char *fmt, ...)
{
	char buf[60];
	int n;
	va_list ap;
	va_start(ap, fmt);
	n = vsnprintf(buf, sizeof(buf), fmt, ap);
	va_end(ap);
	if (addgen_alloc(mem_ctx, p, n + 1) != 0) {
		return -1;
	}
	if (n != 0) {
		memcpy(p->s + p->length, buf, n);
	}
	p->length += n;
	p->s[p->length] = 0;
	return 0;
}

/* 
   this is here to make it easier for people to write dump functions 
   for their own types
 */
int gen_addgen(TALLOC_CTX *mem_ctx, struct parse_string *p, const char *fmt, ...)
{
	char *buf = NULL;
	int n;
	va_list ap;
	va_start(ap, fmt);
	n = vasprintf(&buf, fmt, ap);
	va_end(ap);
	if (addgen_alloc(mem_ctx, p, n + 1) != 0) {
		if (buf) free(buf);
		return -1;
	}
	if (n != 0) {
		memcpy(p->s + p->length, buf, n);
	}
	p->length += n;
	p->s[p->length] = 0;
	if (buf) free(buf);
	return 0;
}

/* dump a enumerated type */
int gen_dump_enum(TALLOC_CTX *mem_ctx,
		  const struct enum_struct *einfo,
		  struct parse_string *p, 
		  const char *ptr,
		  unsigned indent)
{
	unsigned v = *(unsigned *)ptr;
	int i;
	for (i=0;einfo[i].name;i++) {
		if (v == einfo[i].value) {
			addstr(mem_ctx, p, einfo[i].name);
			return 0;
		}
	}
	/* hmm, maybe we should just fail? */
	return gen_dump_unsigned(mem_ctx, p, ptr, indent);
}

/* dump a single non-array element, hanlding struct and enum */
static int gen_dump_one(TALLOC_CTX *mem_ctx,
			struct parse_string *p, 
			const struct parse_struct *pinfo,
			const char *ptr,
			unsigned indent)
{
	if (pinfo->dump_fn == gen_dump_char && pinfo->ptr_count == 1) {
		char *s = encode_bytes(mem_ctx, ptr, strlen(ptr));
		if (addchar(mem_ctx, p,'{') ||
		    addstr(mem_ctx, p, s) ||
		    addstr(mem_ctx, p, "}")) {
			return -1;
		}
		return 0;
	}

	return pinfo->dump_fn(mem_ctx, p, ptr, indent);
}

/* handle dumping of an array of arbitrary type */
static int gen_dump_array(TALLOC_CTX *mem_ctx,
			  struct parse_string *p,
			  const struct parse_struct *pinfo, 
			  const char *ptr,
			  int array_len,
			  int indent)
{
	int i, count=0;

	/* special handling of fixed length strings */
	if (array_len != 0 && 
	    pinfo->ptr_count == 0 &&
	    pinfo->dump_fn == gen_dump_char) {
		char *s = encode_bytes(mem_ctx, ptr, array_len);
		if (!s) return -1;
		if (addtabbed(mem_ctx, p, pinfo->name, indent) ||
		    addstr(mem_ctx, p, " = {") ||
		    addstr(mem_ctx, p, s) ||
		    addstr(mem_ctx, p, "}\n")) {
			return -1;
		}
		return 0;
	}

	for (i=0;i<array_len;i++) {
		const char *p2 = ptr;
		unsigned size = pinfo->size;

		/* generic pointer dereference */
		if (pinfo->ptr_count) {
			p2 = *(const char **)ptr;
			size = sizeof(void *);
		}
		
		if ((count || pinfo->ptr_count) && 
		    !(pinfo->flags & FLAG_ALWAYS) &&
		    all_zero(ptr, size)) {
			ptr += size;
			continue;
		}
		if (count == 0) {
			if (addtabbed(mem_ctx, p, pinfo->name, indent) ||
			    addshort(mem_ctx, p, " = %u:", i)) {
				return -1;
			}
		} else {
			if (addshort(mem_ctx, p, ", %u:", i) != 0) {
				return -1;
			}
		}
		if (gen_dump_one(mem_ctx, p, pinfo, p2, indent) != 0) {
			return -1;
		}
		ptr += size;
		count++;
	}
	if (count) {
		return addstr(mem_ctx, p, "\n");
	}
	return 0;
}

/* find a variable by name in a loaded structure and return its value
   as an integer. Used to support dynamic arrays */
static int find_var(const struct parse_struct *pinfo,
		    const char *data,
		    const char *var)
{
	int i;
	const char *ptr;

	/* this allows for constant lengths */
	if (isdigit(*var)) {
		return atoi(var);
	}

	for (i=0;pinfo[i].name;i++) {
		if (strcmp(pinfo[i].name, var) == 0) break;
	}
	if (!pinfo[i].name) return -1;

	ptr = data + pinfo[i].offset;

	switch (pinfo[i].size) {
	case sizeof(int):
		return *(int *)ptr;
	case sizeof(char):
		return *(char *)ptr;
	}

	return -1;
}


int gen_dump_struct(TALLOC_CTX *mem_ctx,
		    const struct parse_struct *pinfo,
		    struct parse_string *p, 
		    const char *ptr, 
		    unsigned indent)
{
	char *s = gen_dump(mem_ctx, pinfo, ptr, indent+1);
	if (!s) return -1;
	if (addstr(mem_ctx, p, "{\n") || 
	    addstr(mem_ctx, p, s) || 
	    addtabbed(mem_ctx, p, "}", indent)) {
		return -1;
	}
	return 0;
}

static int gen_dump_string(TALLOC_CTX *mem_ctx,
			   struct parse_string *p,
			   const struct parse_struct *pinfo, 
			   const char *data, 
			   unsigned indent)
{
	const char *ptr = *(char **)data;
	char *s = encode_bytes(mem_ctx, ptr, strlen(ptr));
	if (addtabbed(mem_ctx, p, pinfo->name, indent) ||
	    addstr(mem_ctx, p, " = ") ||
	    addchar(mem_ctx, p, '{') ||
	    addstr(mem_ctx, p, s) ||
	    addstr(mem_ctx, p, "}\n")) {
		return -1;
	}
	return 0;
}

/* 
   find the length of a nullterm array
*/
static int len_nullterm(const char *ptr, int size, int array_len)
{
	int len;

	if (size == 1) {
		len = strnlen(ptr, array_len);
	} else {
		for (len=0; len < array_len; len++) {
			if (all_zero(ptr+len*size, size)) break;
		}
	}

	if (len == 0) len = 1;

	return len;
}


/* the generic dump routine. Scans the parse information for this structure
   and processes it recursively */
char *gen_dump(TALLOC_CTX *mem_ctx,
	       const struct parse_struct *pinfo, 
	       const char *data, 
	       unsigned indent)
{
	struct parse_string p;
	int i;
	
	p.length = 0;
	p.allocated = 0;
	p.s = NULL;

	if (addstr(mem_ctx, &p, "") != 0) {
		return NULL;
	}
	
	for (i=0;pinfo[i].name;i++) {
		const char *ptr = data + pinfo[i].offset;
		unsigned size = pinfo[i].size;

		if (pinfo[i].ptr_count) {
			size = sizeof(void *);
		}

		/* special handling for array types */
		if (pinfo[i].array_len) {
			unsigned len = pinfo[i].array_len;
			if (pinfo[i].flags & FLAG_NULLTERM) {
				len = len_nullterm(ptr, size, len);
			}
			if (gen_dump_array(mem_ctx, &p, &pinfo[i], ptr, 
					   len, indent)) {
				goto failed;
			}
			continue;
		}

		/* and dynamically sized arrays */
		if (pinfo[i].dynamic_len) {
			int len = find_var(pinfo, data, pinfo[i].dynamic_len);
			struct parse_struct p2 = pinfo[i];
			if (len < 0) {
				goto failed;
			}
			if (len > 0) {
				if (pinfo[i].flags & FLAG_NULLTERM) {
					len = len_nullterm(*(char **)ptr, 
							   pinfo[i].size, len);
				}
				p2.ptr_count--;
				p2.dynamic_len = NULL;
				if (gen_dump_array(mem_ctx, &p, &p2,
						   *(char **)ptr, 
						   len, indent) != 0) {
					goto failed;
				}
			}
			continue;
		}

		/* don't dump zero elements */
		if (!(pinfo[i].flags & FLAG_ALWAYS) && all_zero(ptr, size)) continue;

		/* assume char* is a null terminated string */
		if (pinfo[i].size == 1 && pinfo[i].ptr_count == 1 &&
		    pinfo[i].dump_fn == gen_dump_char) {
			if (gen_dump_string(mem_ctx, &p, &pinfo[i], ptr, indent) != 0) {
				goto failed;
			}
			continue;
		}

		/* generic pointer dereference */
		if (pinfo[i].ptr_count) {
			ptr = *(const char **)ptr;
		}

		if (addtabbed(mem_ctx, &p, pinfo[i].name, indent) ||
		    addstr(mem_ctx, &p, " = ") ||
		    gen_dump_one(mem_ctx, &p, &pinfo[i], ptr, indent) ||
		    addstr(mem_ctx, &p, "\n")) {
			goto failed;
		}
	}
	return p.s;

failed:
	return NULL;
}

/* search for a character in a string, skipping over sections within
   matching braces */
static char *match_braces(char *s, char c)
{
	int depth = 0;
	while (*s) {
		switch (*s) {
		case '}':
			depth--;
			break;
		case '{':
			depth++;
			break;
		}
		if (depth == 0 && *s == c) {
			return s;
		}
		s++;
	}
	return s;
}

/* parse routine for enumerated types */
int gen_parse_enum(TALLOC_CTX *mem_ctx,
		   const struct enum_struct *einfo, 
		   char *ptr, 
		   const char *str)
{
	unsigned v;
	int i;

	if (isdigit(*str)) {
		if (sscanf(str, "%u", &v) != 1) {
			errno = EINVAL;
			return -1;
		}
		*(unsigned *)ptr = v;
		return 0;
	}

	for (i=0;einfo[i].name;i++) {
		if (strcmp(einfo[i].name, str) == 0) {
			*(unsigned *)ptr = einfo[i].value;
			return 0;
		}
	}

	/* unknown enum value?? */
	return -1;
}


/* parse all base types */
static int gen_parse_base(TALLOC_CTX *mem_ctx,
			  const struct parse_struct *pinfo, 
			  char *ptr, 
			  const char *str)
{
	if (pinfo->parse_fn == gen_parse_char && pinfo->ptr_count==1) {
		unsigned len;
		char *s = decode_bytes(mem_ctx, str, &len);
		if (!s) return -1;
		*(char **)ptr = s;
		return 0;
	}

	if (pinfo->ptr_count) {
		unsigned size = pinfo->ptr_count>1?sizeof(void *):pinfo->size;
		struct parse_struct p2 = *pinfo;
		*(void **)ptr = talloc(mem_ctx, size);
		if (! *(void **)ptr) {
			return -1;
		}
		memset(*(void **)ptr, 0, size);
		ptr = *(char **)ptr;
		p2.ptr_count--;
		return gen_parse_base(mem_ctx, &p2, ptr, str);
	}

	return pinfo->parse_fn(mem_ctx, ptr, str);
}

/* parse a generic array */
static int gen_parse_array(TALLOC_CTX *mem_ctx,
			   const struct parse_struct *pinfo, 
			   char *ptr, 
			   const char *str,
			   int array_len)
{
	char *p, *p2;
	unsigned size = pinfo->size;

	/* special handling of fixed length strings */
	if (array_len != 0 && 
	    pinfo->ptr_count == 0 &&
	    pinfo->dump_fn == gen_dump_char) {
		unsigned len = 0;
		char *s = decode_bytes(mem_ctx, str, &len);
		if (!s || (len > array_len)) return -1;
		memset(ptr, 0, array_len);
		memcpy(ptr, s, len);
		return 0;
	}

	if (pinfo->ptr_count) {
		size = sizeof(void *);
	}

	while (*str) {
		unsigned idx;
		int done;

		idx = atoi(str);
		p = strchr(str,':');
		if (!p) break;
		p++;
		p2 = match_braces(p, ',');
		done = (*p2 != ',');
		*p2 = 0;

		if (*p == '{') {
			p++;
			p[strlen(p)-1] = 0;
		}

		if (gen_parse_base(mem_ctx, pinfo, ptr + idx*size, p) != 0) {
			return -1;
		}

		if (done) break;
		str = p2+1;
	}

	return 0;
}

/* parse one element, hanlding dynamic and static arrays */
static int gen_parse_one(TALLOC_CTX *mem_ctx,
			 const struct parse_struct *pinfo, 
			 const char *name, 
			 char *data, 
			 const char *str)
{
	int i;
	for (i=0;pinfo[i].name;i++) {
		if (strcmp(pinfo[i].name, name) == 0) {
			break;
		}
	}
	if (pinfo[i].name == NULL) {
		return 0;
	}

	if (pinfo[i].array_len) {
		return gen_parse_array(mem_ctx, &pinfo[i],
				       data+pinfo[i].offset, 
				       str, pinfo[i].array_len);
	}

	if (pinfo[i].dynamic_len) {
		int len = find_var(pinfo, data, pinfo[i].dynamic_len);
		if (len < 0) {
			errno = EINVAL;
			return -1;
		}
		if (len > 0) {
			struct parse_struct p2 = pinfo[i];
			char *ptr;
			unsigned size = pinfo[i].ptr_count>1?sizeof(void*):pinfo[i].size;
			ptr = talloc(mem_ctx, len*size);
			if (!ptr) {
				errno = ENOMEM;
				return -1;
			}
			memset(ptr, 0, len*size);
			*((char **)(data + pinfo[i].offset)) = ptr;
			p2.ptr_count--;
			p2.dynamic_len = NULL;
			return gen_parse_array(mem_ctx, &p2, ptr, str, len);
		}
		return 0;
	}

	return gen_parse_base(mem_ctx, &pinfo[i], data + pinfo[i].offset, str);
}

int gen_parse_struct(TALLOC_CTX * mem_ctx, const struct parse_struct *pinfo, char *ptr, const char *str)
{
	return gen_parse(mem_ctx, pinfo, ptr, str);
}

/* the main parse routine */
int gen_parse(TALLOC_CTX *mem_ctx, const struct parse_struct *pinfo, char *data, const char *s)
{
	char *str, *s0;
	
	s0 = talloc_strdup(mem_ctx, s);
	str = s0;

	while (*str) {
		char *p;
		char *name;
		char *value;

		/* skip leading whitespace */
		while (isspace(*str)) str++;

		p = strchr(str, '=');
		if (!p) break;
		value = p+1;
		while (p > str && isspace(*(p-1))) {
			p--;
		}

		*p = 0;
		name = str;

		while (isspace(*value)) value++;

		if (*value == '{') {
			str = match_braces(value, '}');
			value++;
		} else {
			str = match_braces(value, '\n');
		}

		*str++ = 0;
		
		if (gen_parse_one(mem_ctx, pinfo, name, data, value) != 0) {
			return -1;
		}
	}

	return 0;
}



/* for convenience supply some standard dumpers and parsers here */

int gen_parse_char(TALLOC_CTX *mem_ctx, char *ptr, const char *str)
{
	*(unsigned char *)ptr = atoi(str);
	return 0;
}

int gen_parse_int(TALLOC_CTX *mem_ctx, char *ptr, const char *str)
{
	*(int *)ptr = atoi(str);
	return 0;
}

int gen_parse_unsigned(TALLOC_CTX *mem_ctx, char *ptr, const char *str)
{
	*(unsigned *)ptr = strtoul(str, NULL, 10);
	return 0;
}

int gen_parse_time_t(TALLOC_CTX *mem_ctx, char *ptr, const char *str)
{
	*(time_t *)ptr = strtoul(str, NULL, 10);
	return 0;
}

int gen_parse_double(TALLOC_CTX *mem_ctx, char *ptr, const char *str)
{
	*(double *)ptr = atof(str);
	return 0;
}

int gen_parse_float(TALLOC_CTX *mem_ctx, char *ptr, const char *str)
{
	*(float *)ptr = atof(str);
	return 0;
}

int gen_dump_char(TALLOC_CTX *mem_ctx, struct parse_string *p, const char *ptr, unsigned indent)
{
	return addshort(mem_ctx, p, "%u", *(unsigned char *)(ptr));
}

int gen_dump_int(TALLOC_CTX *mem_ctx, struct parse_string *p, const char *ptr, unsigned indent)
{
	return addshort(mem_ctx, p, "%d", *(int *)(ptr));
}

int gen_dump_unsigned(TALLOC_CTX *mem_ctx, struct parse_string *p, const char *ptr, unsigned indent)
{
	return addshort(mem_ctx, p, "%u", *(unsigned *)(ptr));
}

int gen_dump_time_t(TALLOC_CTX *mem_ctx, struct parse_string *p, const char *ptr, unsigned indent)
{
	return addshort(mem_ctx, p, "%u", *(time_t *)(ptr));
}

int gen_dump_double(TALLOC_CTX *mem_ctx, struct parse_string *p, const char *ptr, unsigned indent)
{
	return addshort(mem_ctx, p, "%lg", *(double *)(ptr));
}

int gen_dump_float(TALLOC_CTX *mem_ctx, struct parse_string *p, const char *ptr, unsigned indent)
{
	return addshort(mem_ctx, p, "%g", *(float *)(ptr));
}