/*
 * Copyright (C) 2006 iptelorg GmbH
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Kamailio 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
 *
 * Kamailio 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

/* binrpc is  supposed to be a minimalist binary rpc implementation */



/* packet header:
 * (big endian where it applies)
 *      4b      4b        4b     2b   2b       <var>          <var>
 *  | MAGIC  | VERS  || FLAGS  | LL | CL || total_len ... || cookie ... |
 *  total_len = payload len (doesn't include the packet header)
 *  LL = total length len -1 (number of bytes on which total len is
 *        represented)
 *  CL = cookie length -1 (number of bytes on which the cookie is represented)
 *  E.g.: LL= 0 => total_len is represented on 1 byte (LL+1)
 *        CL= 3 => cookie is represneted on 4 bytes (CL+1)
 */
/* record format:
 *  1b   3b     4b
 * |S | size |  type  || <optional value len> ... || <optional value> ... ||
 *
 * if S==0, size is the size (in bytes) of the value (if size==0 => null value)
 * if S==1, optional_value_len is present, and size is it's size
 *    (if size==0 => and type==array or struct => marks end, else
 *     error, reserved)
 *  Examples:
 *     int (type=0) 0x1234     -> 0x20 0x12 0x34           (optimal)
 *                                0x90 0x02 0x12 0x34      (suboptimal)
 *                                0xA0 0x00 0x02 0x12 0x34 (even worse)
 *                   0x07      -> 0x10 0x07                (optimal)
 *                   0x00      -> 0x00                     (optimal)
 *                                0x10 0x00
 *
 *     str (type=1) - strings are 0 terminated (an extra 0 is added to them
 *                    to make the format easier to parse when asciiz strings
 *                    are required); the length includes the terminating 0
 *                  "abcdef"   -> 0x71 "abcdef" 0x00
 *                  "abcdefhij"-> 0x91 0x0A "abcdefhij" 0x00
 *                  ""         -> 0x11 0x00     (0 len str)
 *     65535 bytes
 *     (using str for it)      -> 0xB1 0x01 0x00 0x00 array 0x00
 *
 *     bytes (type=6) -like str but not 0 terminated
 *                  "abcdef"   -> 0x66 "abcdef"
 *     65535 bytes   *         -> 0xA6 0xff 0xff bytes
 *
 *     arrays (array type=4)
 *       arrays are implemented as anonymous value lists:
 *         array_start value1, value2, ..., array_end
 *                               (start) (1st int)  (2nd elem)  (end array)
 *      ints   [ 0x01 0x02 ]   -> 0x04   0x10 0x01  0x10 0x02        0x84
 *      combo  [ 0x07 "abc"]   -> 0x04   0x10 0x07  0x41 "abc" 0x00  0x84
 *
 *      structs (struct type=3)
 *       structs are implemented as avp list:
 *           struct_start, avp1, avp2 .., struct_end
 *        an avp is a named value pair:  name, value. 
 *          - name behaves like a normal string, but has a diff. type (5)
 *          - avps are legal only inside structs.
 *        avp example:           name part (str)   val part (int here) 
 *         "test", int 0x0b   -> 0x55 "test" 0x00   0x10 0x0b
 *
 *      struct example:
 *                              (start)  (avps)                           (end)
 *      struct{                  0x03        (name )        (val)
 *           intval: int 0x3  ->       0x75 "intval" 0x00 0x10 0x3
 *           s:      str "abc"-        0x25 "s" 0x00      0x41 "abc" 0x00
 *      }                                                                  0x83
 *
 *      Limitations: for now avps cannot have array values =>
 *                   structs cannot contain arrays.
 */


#ifndef _binrpc_h
#define _binrpc_h


#include "../../core/str.h"
#include <string.h>

#define BINRPC_MAGIC 0xA
#define BINRPC_VERS  1

/* sizes & offsets */
#define BINRPC_FIXED_HDR_SIZE	2
#define BINRPC_TLEN_OFFSET		BINRPC_FIXED_HDR_SIZE
#define BINRPC_MIN_HDR_SIZE	(BINRPC_FIXED_HDR_SIZE+2)
#define BINRPC_MAX_HDR_SIZE	(BINRPC_FIXED_HDR_SIZE+4+4)
#define BINRPC_MIN_RECORD_SIZE	1
/* min pkt size: min header + min. len (1) + min. cookie (1)*/
#define BINRPC_MIN_PKT_SIZE	BINRPC_MIN_HDR_SIZE

/* message types */
#define BINRPC_REQ   0
#define BINRPC_REPL  1
#define BINRPC_FAULT 3

/* values types */
#define BINRPC_T_INT	0
#define BINRPC_T_STR	1 /* 0 term, for easier parsing */
#define BINRPC_T_DOUBLE	2
#define BINRPC_T_STRUCT	3
#define BINRPC_T_ARRAY	4
#define BINRPC_T_AVP	5  /* allowed only in structs */
#define BINRPC_T_BYTES	6 /* like STR, but not 0 term */

#define BINRPC_T_ALL	0xf /* wildcard type, will match any record type
							   in the packet (not allowed inside the pkt)*/

/* errors */
#define E_BINRPC_INVAL		-1  /* invalid function call parameters */
#define E_BINRPC_OVERFLOW	-2  /* buffer overflow */
#define E_BINRPC_BADPKT		-3  /* something went really bad, the packet is
								   corrupted*/
#define E_BINRPC_MORE_DATA	-4 /* parsing error: more bytes are needed,
								  just repeat the failed op, when you have
								  more bytes available */
#define E_BINRPC_EOP		-5	/* end of packet reached */
#define E_BINRPC_NOTINIT	-6  /* parse ctx not initialized */
#define E_BINRPC_TYPE		-7  /* unknown type for record, or requested
								   type doesn't match record type */
#define E_BINRPC_RECORD		-8  /* bad record (unexpected, bad struct a.s.o)*/
#define E_BINRPC_BUG		-9  /* internal error, bug */
#define E_BINRPC_LAST		-10 /* used to count the errors, keep always
								   last */

/* flags */
#define BINRPC_F_INIT	1

struct binrpc_pkt{  /* binrpc body */
	unsigned char* body;
	unsigned char* end;
	unsigned char* crt; /*private */
};


struct binrpc_parse_ctx{
	/* header */
	unsigned int tlen; /* total len */
	unsigned int cookie;
	int type; /* request, reply, error */
	
	/* parsing info */
	unsigned int flags;   /* parsing flags */
	unsigned int offset; /* current offset (inside payload) */
	unsigned int in_struct;
	unsigned int in_array;
};



struct binrpc_val{
	str name; /* used only in structs */
	int type;
	union{
		str strval;
		double fval;
		int intval;
		int end;
	}u;
};



/*  helper functions  */

/* return int size: minimum number of bytes needed to represent it
 * if i=0 returns 0 */
inline static int binrpc_get_int_len(int i)
{
	int size;
	for (size=4; size && ((i & (0xffu<<24))==0); i<<=8, size--);
	return size;
}



/* adds a start or end tag (valid only for STRUCT or ARRAY for now */
inline static int binrpc_add_tag(struct binrpc_pkt* pkt, int type, int end)
{
	if (pkt->crt>=pkt->end) return E_BINRPC_OVERFLOW;
	*pkt->crt=(end<<7)|type;
	pkt->crt++;
	return 0;
}



/*  writes a minimal long long, returns the new offset and sets
 * len to the number of bytes written (<=8)
 * to check for oveflow use:  returned_value-p != *len
 * (Note: if *len==0 using the test above succeeds even if p>=end)
 */
inline static unsigned char* binrpc_write_llong(	unsigned char* p,
												unsigned char* end,
												long long i, int *len)
{
	int size;
	unsigned long long u;

	u = (unsigned long long)i;

	for (size=8; size && ((u & (0xffull<<56))==0); u<<=8, size--);
	*len=size;
	for(; (p<end) && (size); p++, size--){
		*p=(unsigned char)(u>>56);
		u<<=8;
	}
	return p;
}



/*  writes a minimal int, returns the new offset and sets
 * len to the number of bytes written (<=4)
 * to check for oveflow use:  returned_value-p != *len
 * (Note: if *len==0 using the test above succeeds even if p>=end)
 */
inline static unsigned char* binrpc_write_int(	unsigned char* p,
												unsigned char* end,
												int i, int *len)
{
	int size;
	unsigned int u;

	u = (unsigned int)i;

	for (size=4; size && ((u & (0xffu<<24))==0); u<<=8, size--);
	*len=size;
	for(; (p<end) && (size); p++, size--){
		*p=(unsigned char)(u>>24);
		u<<=8;
	}
	return p;
}



/* API functions */

/* initialize a binrpc_pkt structure, for packet creation
 * params: pkt         - binrpc body structure that will be initialized 
 *         buf, b_len  -  destination buffer/len
 * returns -1 on error, 0 on success
 *
 * Example usage:
 *  binrpc_init_pkt(pkt, body, BODY_SIZE);
 *  binrpc_addint(pkt, 1);
 *  binrpc_addstr(pkt, "test", sizeof("test")-1);
 *  ...
 *  bytes=binrpc_build_hdr(pkt, BINRPC_REQ, 0x123, hdr_buf, HDR_BUF_LEN);
 *  writev(sock, {{ hdr, bytes}, {pkt->body, pkt->crt-pkt->body}} , 2)*/
inline static int binrpc_init_pkt(struct binrpc_pkt *pkt,
								  unsigned char* buf, int b_len)
{
	if (b_len<BINRPC_MIN_RECORD_SIZE)
		return E_BINRPC_OVERFLOW;
	pkt->body=buf;
	pkt->end=buf+b_len;
	pkt->crt=pkt->body;
	return 0;
};



/* used to update internal contents if the original buffer 
 * (from binrpc_init_pkt) was realloc'ed (and has grown) */
inline static int binrpc_pkt_update_buf(struct binrpc_pkt *pkt,
										unsigned char* new_buf,
										int new_len)
{
	if ((int)(pkt->crt-pkt->body)>new_len){
		return E_BINRPC_OVERFLOW;
	}
	pkt->crt=new_buf+(pkt->crt-pkt->body);
	pkt->body=new_buf;
	pkt->end=new_buf+new_len;
	return 0;
}



/* builds a binrpc header for the binrpc pkt. body pkt and writes it in buf
 * params:  
 *          type     - binrpc packet type (request, reply, fault)
 *          body_len - body len
 *          cookie   - binrpc cookie value
 *          buf,len  - destination buffer & len
 * returns -1 on error, number of bytes written on success */
inline static int binrpc_build_hdr(	int type, int body_len,
									unsigned int cookie,
									unsigned char* buf, int b_len) 
{
	unsigned char* p;
	int len_len;
	int c_len;
	
	len_len=binrpc_get_int_len(body_len);
	c_len=binrpc_get_int_len(cookie);
	if (len_len==0) len_len=1; /* we can't have 0 len */
	if (c_len==0) c_len=1;  /* we can't have 0 len */
	/* size check: 2 bytes header + len_len + cookie len*/
	if (b_len<(BINRPC_FIXED_HDR_SIZE+len_len+c_len)){
		goto error_len;
	}
	p=buf;
	*p=(BINRPC_MAGIC << 4) | BINRPC_VERS;
	p++;
	*p=(type<<4)|((len_len-1)<<2)|(c_len-1);
	p++;
	for(;len_len>0; len_len--,p++){
		*p=(unsigned char)(body_len>>((len_len-1)*8));
	}
	for(;c_len>0; c_len--,p++){
		*p=(unsigned char)(cookie>>((c_len-1)*8));
	}
	return (int)(p-buf);
error_len:
	return E_BINRPC_OVERFLOW;
}



#define binrpc_pkt_len(pkt)		((int)((pkt)->crt-(pkt)->body))



/* changes the length of a header (enough space must be availale) */
inline static int binrpc_hdr_change_len(unsigned char* hdr, int hdr_len,
										int new_len)
{
	int len_len;
	
	binrpc_write_int(&hdr[BINRPC_TLEN_OFFSET], hdr+hdr_len, new_len, &len_len);
	return 0;
}


/* int format:     size TYPE <val>  */
inline static int binrpc_add_llong_type(struct binrpc_pkt* pkt, long long i, int type)
{

	unsigned char* p;
	int size;

	p=binrpc_write_llong(pkt->crt+1, pkt->end, i, &size);
	if ((pkt->crt>=pkt->end) || ((int)(p-pkt->crt-1)!=size))
		goto error_len;
	*(pkt->crt)=(size<<4) | type;
	pkt->crt=p;
	return 0;
error_len:
	return E_BINRPC_OVERFLOW;
}


/* int format:     size BINRPC_T_INT <val>  */
inline static int binrpc_add_int_type(struct binrpc_pkt* pkt, int i, int type)
{
	
	unsigned char* p;
	int size;
	
	p=binrpc_write_int(pkt->crt+1, pkt->end, i, &size);
	if ((pkt->crt>=pkt->end) || ((int)(p-pkt->crt-1)!=size))
		goto error_len;
	*(pkt->crt)=(size<<4) | type;
	pkt->crt=p;
	return 0;
error_len:
	return E_BINRPC_OVERFLOW;
}



/* double format:  FIXME: for now a hack: fixed point represented in
 *  a long long (=> max 3 decimals, < MAX_LLONG/1000) */
#define binrpc_add_double_type(pkt, f, type)\
	binrpc_add_llong_type((pkt), (long long)((f)*1000), (type))



/* skip bytes bytes (leaves an empty space, for possible future use)
 * WARNING: use with care, low level function
 */
inline static int binrpc_add_skip(struct binrpc_pkt* pkt, int bytes)
{

	if ((pkt->crt+bytes)>=pkt->end)
		return E_BINRPC_OVERFLOW;
	pkt->crt+=bytes;
	return 0;
}



/*
 * adds only the string mark and len, you'll have to memcpy the contents
 * manually later (and also use binrpc_add_skip(pkt, l) or increase
 *  pkt->crt directly if you want to continue adding to this pkt).
 *  Usefull for optimizing str writing (e.g. writev(iovec))
 *  WARNING: use with care, low level function, binrpc_addstr or
 *           binrpc_add_str_type are probably what you want.
 *  WARNING1: BINRPC_T_STR and BINRPC_T_AVP must be  0 term, the len passed to
 *            this function, must include the \0 in this case.
 */
inline static int binrpc_add_str_mark(struct binrpc_pkt* pkt, int type,
										int l)
{
	int size;
	unsigned char* p;

	if (pkt->crt>=pkt->end) goto error_len;
	if (l<8){
		size=l;
		p=pkt->crt+1;
	}else{ /* we need a separate len */
		p=binrpc_write_int(pkt->crt+1, pkt->end, l, &size);
		if (((int)(p-pkt->crt-1)!=size))
			goto error_len;
		size|=8; /* mark it as having external len  */
	}
	*(pkt->crt)=(size)<<4|type;
	pkt->crt=p;
	return 0;
error_len:
	return E_BINRPC_OVERFLOW;
}



inline static int binrpc_add_str_type(struct binrpc_pkt* pkt, char* s, int len,
										int type)
{
	int size;
	int l;
	int zero_term; /* whether or not to add an extra 0 at the end */
	unsigned char* p;

	zero_term=((type==BINRPC_T_STR)||(type==BINRPC_T_AVP));
	l=len+zero_term;
	if (l<8){
		size=l;
		p=pkt->crt+1;
	}else{ /* we need a separate len */
		p=binrpc_write_int(pkt->crt+1, pkt->end, l, &size);
		/* if ((int)(p-pkt->crt)<(size+1)) goto error_len;  - not needed,
		 *  caught by the next check */
		size|=8; /* mark it as having external len  */
	}
	if ((p+l)>pkt->end) goto error_len;
	*(pkt->crt)=(size)<<4|type;
	memcpy(p, s, len);
	if (zero_term) p[len]=0;
	pkt->crt=p+l;
	return 0;
error_len:
	return E_BINRPC_OVERFLOW;
}



/* adds an avp (name, value) pair, useful to add structure members */
inline static int binrpc_addavp(struct binrpc_pkt* pkt, struct binrpc_val* avp)
{
	int ret;
	unsigned char* bak;

	bak=pkt->crt;
	ret=binrpc_add_str_type(pkt, avp->name.s, avp->name.len, BINRPC_T_AVP);
	if (ret<0) return ret;
	switch (avp->type){
		case BINRPC_T_INT:
			ret=binrpc_add_int_type(pkt, avp->u.intval, avp->type);
			break;
		case BINRPC_T_STR:
		case BINRPC_T_BYTES:
			ret=binrpc_add_str_type(pkt, avp->u.strval.s,
										avp->u.strval.len,
										avp->type);
			break;
		case BINRPC_T_STRUCT:
		case BINRPC_T_ARRAY:
			ret=binrpc_add_tag(pkt, avp->type, 0);
			break;
		case BINRPC_T_DOUBLE:
			ret=binrpc_add_double_type(pkt, avp->u.fval, avp->type);
			break;
		default:
			ret=E_BINRPC_BUG;
	}
	if (ret<0)
		pkt->crt=bak; /* roll back */
	return ret;
}



#define binrpc_addint(pkt, i)	binrpc_add_int_type((pkt), (i), BINRPC_T_INT) 

#define binrpc_adddouble(pkt, f)	\
	binrpc_add_double_type((pkt), (f), BINRPC_T_DOUBLE)

#define binrpc_addstr(pkt, s, len)	\
	binrpc_add_str_type((pkt), (s), (len), BINRPC_T_STR) 

#define binrpc_addbytes(pkt, s, len)	\
	binrpc_add_str_type((pkt), (s), (len), BINRPC_T_BYTES) 

/* struct type format:
 *  start :         0000 | BINRPC_T_STRUCT 
 *  end:            1000 | BINRPC_T_STRUCT
 */
#define  binrpc_start_struct(pkt) binrpc_add_tag((pkt), BINRPC_T_STRUCT, 0)

#define  binrpc_end_struct(pkt) binrpc_add_tag((pkt), BINRPC_T_STRUCT, 1)

#define  binrpc_start_array(pkt) binrpc_add_tag((pkt), BINRPC_T_ARRAY, 0)

#define  binrpc_end_array(pkt) binrpc_add_tag((pkt), BINRPC_T_ARRAY, 1)


static inline int binrpc_addfault(	struct binrpc_pkt* pkt,
									int code,
									char* s, int len)
{
	int ret;
	unsigned char* bak;
	
	bak=pkt->crt;
	if ((ret=binrpc_addint(pkt, code))<0)
		return ret;
	ret=binrpc_addstr(pkt, s, len);
	if (ret<0)
		pkt->crt=bak; /* roll back */
	return ret;
}

/* parsing incoming messages */


static inline unsigned char* binrpc_read_llong(	long long* i,
												int len,
												unsigned char* s,
												unsigned char* end,
												int *err
												)
{
	unsigned char* start;
	unsigned long long u;

	start=s;
	*i=0;
	u = 0;
	*err=0;
	for(;len>0; len--, s++){
		if (s>=end){
			*err=E_BINRPC_MORE_DATA;
			*i = (long long)u;
			return start;
		}
		u<<=8;
		u|=*s;
	};
	*i = (long long)u;
	return s;
}



static inline unsigned char* binrpc_read_int(	int* i,
												int len,
												unsigned char* s,
												unsigned char* end,
												int *err
												)
{
	unsigned char* start;
	unsigned int u;

	start=s;
	*i=0;
	u = 0;
	*err=0;
	for(;len>0; len--, s++){
		if (s>=end){
			*err=E_BINRPC_MORE_DATA;
			*i = (int)u;
			return start;
		}
		u<<=8;
		u|=*s;
	};
	*i = (int)u;
	return s;
}



/* initialize parsing context, it tries to read the whole message header,
 * if there is not enough data, sets *err to E_BINRPC_MORE_DATA. In this
 *  case just redo the call when more data is available (len is bigger)
 * on success sets *err to 0 and returns the current position in  buf
 * (=> you can discard the content between buf & the returned value).
 * On error buf is returned back, and *err set.
 */
static inline unsigned char* binrpc_parse_init(	struct binrpc_parse_ctx* ctx,
												unsigned char* buf,
												int len,
												int *err
												)
{
	int len_len, c_len;
	unsigned char *p;

	*err=0;
	ctx->tlen=0;	/* init to 0 */
	ctx->cookie=0;	/* init to 0 */
	if (len<BINRPC_MIN_PKT_SIZE){
		*err=E_BINRPC_MORE_DATA;
		goto error;
	}
	if (buf[0]!=((BINRPC_MAGIC<<4)|BINRPC_VERS)){
		*err=E_BINRPC_BADPKT;
		goto error;
	}
	ctx->type=buf[1]>>4;
	/* type check */
	switch(ctx->type){
		case BINRPC_REQ:
		case BINRPC_REPL:
		case BINRPC_FAULT:
			break;
		default:
			*err=E_BINRPC_BADPKT;
			goto error;
	}
	len_len=((buf[1]>>2) & 3) + 1;
	c_len=(buf[1]&3) + 1;
	if ((BINRPC_TLEN_OFFSET+len_len+c_len)>len){
		*err=E_BINRPC_MORE_DATA;
		goto error;
	}
	p=binrpc_read_int((int*)&ctx->tlen, len_len, &buf[BINRPC_TLEN_OFFSET],
						&buf[len], err);
	/* empty packets (replies) are allowed
	   if (ctx->tlen==0){
		*err=E_BINRPC_BADPKT;
		goto error;
	} */
	p=binrpc_read_int((int*)&ctx->cookie, c_len, p, &buf[len], err);
	ctx->offset=0;
	ctx->flags|=BINRPC_F_INIT;
	return p;
error:
	return buf;
}



/* returns bytes needed (till the end of the packet)
 * on error (non. init ctx) returns < 0 
 */
inline static int binrpc_bytes_needed(struct binrpc_parse_ctx *ctx)
{
	if (ctx->flags & BINRPC_F_INIT)
		return ctx->tlen-ctx->offset;
	return E_BINRPC_NOTINIT;
}



/* prefill v with the requested type, if type==BINRPC_T_ALL it 
 * will be replaced by the actual record type 
 * known problems: no support for arrays inside STRUCT
 * param smode: allow simple vals inside struct (needed for 
 * not-strict-formatted rpc responses)
 * returns position after the record and *err==0 if successful
 *         original position and *err<0 if not */
inline static unsigned char* binrpc_read_record(struct binrpc_parse_ctx* ctx,
												unsigned char* buf,
												unsigned char* end,
												struct binrpc_val* v,
												int smode,
												int* err
												)
{
	int type;
	int len;
	int end_tag;
	int tmp;
	unsigned char* p;
	long long ll;

	p=buf;
	end_tag=0;
	*err=0;
	if (!(ctx->flags & BINRPC_F_INIT)){
		*err=E_BINRPC_NOTINIT;
		goto error;
	}
	if (ctx->offset>=ctx->tlen){
		*err=E_BINRPC_EOP;
		goto error;
	}
	if (p>=end){
		*err=E_BINRPC_MORE_DATA;
		goto error;
	}
	/* read type_len */
	type=*p & 0xf;
	len=*p>>4;
	p++;
	if ((type!=BINRPC_T_DOUBLE) && (len & 8)){
		end_tag=1; /* possible end mark for array or structs */
		/* we have to read len bytes and use them as the new len */
		p=binrpc_read_int(&len, len&7, p, end, err);
		if (*err<0)
			goto error;
	}
	if ((p+len)>end){
		*err=E_BINRPC_MORE_DATA;
		goto error;
	}
	if ((v->type!=type) && (v->type !=BINRPC_T_ALL)){
		goto error_type;
	}
	v->type=type;
	switch(type){
		case BINRPC_T_STRUCT:
			if (ctx->in_struct){
				if (end_tag){
					ctx->in_struct--;
					v->u.end=1;
				}else{
					if(smode==0) {
						goto error_record;
					} else {
						v->u.end=0;
						ctx->in_struct++;
					}
				}
			} else {
				if (end_tag)
					goto error_record;
				v->u.end=0;
				ctx->in_struct++;
			}
			break;
		case BINRPC_T_AVP:
			/* name | value */
			if (ctx->in_struct){
				v->name.s=(char*)p;
				v->name.len=(len-1); /* don't include 0 term */
				p+=len;
				if (p>=end){
					*err=E_BINRPC_MORE_DATA;
					goto error;
				}
				/* avp value type */
				type=*p & 0xf;
				if ((type!=BINRPC_T_AVP) && (type!=BINRPC_T_ARRAY)){
					tmp=ctx->in_struct;
					ctx->in_struct=0; /* hack to parse a normal record */
					v->type=type; /* hack */
					p=binrpc_read_record(ctx, p, end, v, smode, err);
					if (*err<0){
						ctx->in_struct=tmp;
						goto error;
					}else{
						ctx->in_struct+=tmp;
						/* the offset is already updated => skip */
						goto no_offs_update;
					}
				}else{
					goto  error_record;
				}
			} else {
				goto error_type;
			}
			break;
		case BINRPC_T_INT:
			if (ctx->in_struct && smode==0) goto error_record;
			p=binrpc_read_int(&v->u.intval, len, p, end, err);
			break;
		case BINRPC_T_STR:
			if (ctx->in_struct && smode==0) goto error_record;
			v->u.strval.s=(char*)p;
			v->u.strval.len=(len-1); /* don't include terminating 0 */
			p+=len;
			break;
		case BINRPC_T_BYTES:
			if (ctx->in_struct && smode==0) goto error_record;
			v->u.strval.s=(char*)p;
			v->u.strval.len=len;
			p+=len;
			break;
		case BINRPC_T_ARRAY:
			if (ctx->in_struct && smode==0) goto error_record;
			if (end_tag){
				if (ctx->in_array>0){
					ctx->in_array--;
					v->u.end=1;
				}else{
					goto error_record;
				}
			}else{
				ctx->in_array++;
				v->u.end=0;
			}
			break;
		case BINRPC_T_DOUBLE: /* FIXME: hack: represented as fixed point
		                                      inside a long long */
			if (ctx->in_struct && smode==0) goto error_record;
			p=binrpc_read_llong(&ll, len, p, end, err);
			v->u.fval=((double)ll)/1000;
			break;
		default:
			if (ctx->in_struct){
				goto error_record;
			} else {
				goto error_type;
			}
	}
	ctx->offset+=(int)(p-buf);
no_offs_update:
	return p;
error_type:
	*err=E_BINRPC_TYPE;
	return buf;
error_record:
	*err=E_BINRPC_RECORD;
error:
	return buf;
}



/* reads/skips an entire struct
 * the struct start/end are saved in v->u.strval.s, v->u.strval.len 
 * return:  - new buffer position  and set *err to 0 if successfull
 *          - original buffer and *err<0 on error */
inline static unsigned char* binrpc_read_struct(struct binrpc_parse_ctx* ctx,
												unsigned char* buf,
												unsigned char* end,
												struct binrpc_val* v,
												int* err
												)
{

	int type;
	int len;
	int end_tag;
	unsigned char* p;
	int in_struct;
	
	*err=0;
	p=buf;
	end_tag=0;
	if (!(ctx->flags & BINRPC_F_INIT)){
		*err=E_BINRPC_NOTINIT;
		goto error;
	}
	if (ctx->offset>=ctx->tlen){
		*err=E_BINRPC_EOP;
		goto error;
	}
	if (p>=end){
		*err=E_BINRPC_MORE_DATA;
		goto error;
	}
	/* read type_len */
	type=*p & 0xf;
	len=*p>>4;
	p++;
	if (len & 8){
		end_tag=1; /* possible end mark for array or structs */
		/* we have to read len bytes and use them as the new len */
		p=binrpc_read_int(&len, len&7, p, end, err);
		if (*err<0)
			goto error;
	}
	if ((p+len)>=end){
		*err=E_BINRPC_MORE_DATA;
		goto error;
	}
	if (type!=BINRPC_T_STRUCT){
		goto error_type;
	}
	if (end_tag){
		goto error_record;
	}
	p+=len; /* len should be 0 for a struct tag */
	in_struct=1;
	v->type=type;
	v->u.strval.s=(char*)p; /* it will conain the inside of the struc */
	while(in_struct){
		/* read name */
		type=*p & 0xf;
		len=*p>>4;
		p++;
		if (len & 8){
			end_tag=1; /* possible end mark for array or structs */
			/* we have to read len bytes and use them as the new len */
			p=binrpc_read_int(&len, len&7, p, end, err);
			if (*err<0)
				goto error;
		}
		if ((type==BINRPC_T_STRUCT) && end_tag){
			in_struct--;
			if (in_struct<0)
				goto error_record;
			continue;
		}else if (type!=BINRPC_T_AVP){
			goto error_record;
		}
		/* skip over it */
		p+=len;
		if (p>=end){
			*err=E_BINRPC_MORE_DATA;
			goto error;
		}
		/* read value */
		type=*p & 0xf;
		len=*p>>4;
		p++;
		if (len & 8){
			end_tag=1; /* possible end mark for array or structs */
			/* we have to read len bytes and use them as the new len */
			p=binrpc_read_int(&len, len&7, p, end, err);
			if (*err<0)
				goto error;
		}
		if (type==BINRPC_T_STRUCT){
			if (end_tag)
				goto error_record;
			in_struct++;
		};
		p+=len;
		if (p>=end){
			*err=E_BINRPC_MORE_DATA;
			goto error;
		}
	}
	/* don't include the end tag */;
	v->u.strval.len=(int)(p-(unsigned char*)v->u.strval.s)-1;
	return p;
	
error_type:
	*err=E_BINRPC_RECORD;
	return buf;
error_record:
	*err=E_BINRPC_TYPE;
error:
	return buf;
}



/* error code to string */
const char* binrpc_error(int err);
#endif
