/*
 * 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
 */


#include "binrpc.h"
#include "../../core/dprint.h"
#include "../../core/rpc.h"
#include "../../core/sr_module.h"
#include "../../core/mem/mem.h"
#include "../../core/clist.h"
#include "io_listener.h"
#include "ctl.h"

#include <stdio.h>  /* vsnprintf */
#include <stdlib.h> /* strtod */
#include <stdarg.h>

#define DEFAULT_RPC_PRINTF_BUF_SIZE	1024

/* if set try to automatically convert values to the requested type in
   rpc->scan (default: not set) */
int autoconvert=0;

int binrpc_max_body_size = 32; /* multiplied by 1024 in mod init */
int binrpc_struct_max_body_size = 8; /* multiplied by 1024 in mod init */
int binrpc_buffer_size = DEFAULT_RPC_PRINTF_BUF_SIZE;

#define BINRPC_MAX_BODY	binrpc_max_body_size  /* maximum body for send */
#define STRUCT_MAX_BODY	binrpc_struct_max_body_size
#define MAX_MSG_CHUNKS	96

#define BINRPC_GC_IBSIZE 4 /* initial gc block size (pointers no.) */

struct rpc_struct_head{
	struct rpc_struct_l* next;
	struct rpc_struct_l* prev;
};


struct rpc_struct_l{
	struct rpc_struct_l* next;
	struct rpc_struct_l* prev;
	struct binrpc_pkt pkt;
	struct rpc_struct_head substructs; /* head */
	int offset; /* byte offset in parent's pkt */
};

struct binrpc_send_ctx{
	struct binrpc_pkt pkt; /* body */
	struct rpc_struct_head structs; /* list head */
};

struct binrpc_recv_ctx{
	struct binrpc_parse_ctx ctx;
	unsigned char*  s; /* current position in buffer */
	unsigned char* end;
	int record_no;
	int in_struct;
};


struct binrpc_gc_block{
	unsigned short p_no; /**< array size */
	unsigned short idx;  /**< current/last used pos. in the array */
	struct binrpc_gc_block* next;
	void* p[1]; /**< array of pointers that will be free'd */
};


struct binrpc_ctx{
	struct binrpc_recv_ctx in;
	struct binrpc_send_ctx out;
	void* send_h; /* send handle */
	char* method;
	struct binrpc_gc_block* gc; /**< garbage collection */
	int replied;
	int err_code;
	str err_phrase;	/**< Leading zero must be included! */
};


struct iovec_array{
	struct iovec* v;
	int idx;
	int len;
	void *ctx;
};

/* send */
static void rpc_fault(struct binrpc_ctx* ctx, int code, char* fmt, ...);
static int rpc_send(struct binrpc_ctx* ctx);
static int rpc_send_v(struct iovec_array *a);
static int rpc_add(struct binrpc_ctx* ctx, char* fmt, ...);
static int rpc_scan(struct binrpc_ctx* ctx, char* fmt, ...);
static int rpc_rpl_printf(struct binrpc_ctx* ctx, char* fmt, ...);
static int rpc_struct_add(struct rpc_struct_l* s, char* fmt, ...);
static int rpc_array_add(struct rpc_struct_l* s, char* fmt, ...);
static int rpc_struct_scan(struct rpc_struct_l* s, char* fmt, ...);
/* struct scan */
static int rpc_struct_printf(struct rpc_struct_l *s, char* name,
								char* fmt, ...);


static rpc_t binrpc_callbacks;

void binrpc_callbacks_init(void)
{
	memset(&binrpc_callbacks, 0, sizeof(binrpc_callbacks));
	binrpc_callbacks.fault         = (rpc_fault_f)rpc_fault;
	binrpc_callbacks.send          = (rpc_send_f)rpc_send;
	binrpc_callbacks.add           = (rpc_add_f)rpc_add;
	binrpc_callbacks.scan          = (rpc_scan_f)rpc_scan;
	binrpc_callbacks.rpl_printf    = (rpc_rpl_printf_f)rpc_rpl_printf;
	binrpc_callbacks.struct_add    = (rpc_struct_add_f)rpc_struct_add;
	binrpc_callbacks.array_add     = (rpc_struct_add_f)rpc_array_add;
	binrpc_callbacks.struct_scan   = (rpc_struct_scan_f)rpc_struct_scan;
	binrpc_callbacks.struct_printf = (rpc_struct_printf_f)rpc_struct_printf;
}

/** mark a pointer for freeing when the ctx is destroyed.
 * @return 0 on success, -1 on error
 */
inline static int binrpc_gc_track(struct binrpc_ctx* ctx, void* p)
{
	struct binrpc_gc_block* b;
	int n;
	
	b=ctx->gc;
	if (b==0 || (b->idx>=b->p_no)){
		n=(b==0)?BINRPC_GC_IBSIZE:b->p_no*2;
		b=ctl_malloc(sizeof(*b)+n*sizeof(void*)-sizeof(b->p));
		if (b==0)
			return -1;
		b->p_no=n;
		b->idx=0;
		/* link in front */
		b->next=ctx->gc;
		ctx->gc=b;
	}
	b->p[b->idx]=p;
	b->idx++;
	return 0;
}



/** free all the tracked pointer from ctx->gc.
 */
inline static void binrpc_gc_collect(struct binrpc_ctx* ctx)
{
	struct binrpc_gc_block* b;
	struct binrpc_gc_block* next;
	int i;
	
	for(b=ctx->gc; b; b=next){
		next=b->next;
		for (i=0; i<b->idx; i++)
			ctl_free(b->p[i]);
		ctl_free(b);
	}
	ctx->gc=0;
}


static struct rpc_struct_l* new_rpc_struct()
{
	struct rpc_struct_l* rs;
	
	/* alloc everything in one chunk */
	rs=ctl_malloc(sizeof(struct rpc_struct_l)+STRUCT_MAX_BODY);
	if (rs==0)
		goto error;
	memset(rs, 0, sizeof(struct rpc_struct_l));
	clist_init(&rs->substructs, next, prev);
	if (binrpc_init_pkt(&rs->pkt,
				(unsigned char*)rs+sizeof(struct rpc_struct_l),
				STRUCT_MAX_BODY)<0){
		ctl_free(rs);
		goto error;
	}
	return rs;
error:
	return 0;
}



#if 0 /* not used yet */
/* doubles the size */
static struct rpc_struct_l* grow_rpc_struct(struct rpc_struct_l *rs)
{
	
	struct rpc_struct_l* new_rs;
	int csize; /* body */
	
	csize=binrpc_pkt_len(&rs->pkt);
	csize*=2;
	new_rs=ctl_realloc(rs, sizeof(struct rpc_struct_l)+csize);
	if (new_rs){
		binrpc_pkt_update_buf(&rs->pkt, 
							(unsigned char*)new_rs+sizeof(struct rpc_struct_l),
							csize);
	}
	return new_rs;
}
#endif


#if 0
/* appends buf to an already init. binrpc_pkt */
inline static int append_pkt_body(struct binrpc_pkt* p, unsigned char* buf,
							int len)
{
	
	if ((int)(p->end-p->crt)<len){
		goto error;
#if 0
		size=2*(int)(p->end-p->body);
		offset=binrpc_pkt_len(p);
		for(;(size-offset)<len; size*=2); /* find new size */
		new_b=ctl_realloc(p->body, size);
		if (new_b==0)
			goto error;
		binrpc_pkt_update_buf(p, new_b, size);
#endif
	}
	memcpy(p->crt, buf, len);
	p->crt+=len;
	return 0;
error:
	return -1; /* buff. overflow */
}
#endif


inline static int append_iovec(struct iovec_array* a, unsigned char* buf,
								int len)
{
	int ret;

	if (a->idx >= a->len) {
		ret = rpc_send_v(a);
		if (ret < 0)
			return ret;
	}
	a->v[a->idx].iov_base=buf;
	a->v[a->idx].iov_len=len;
	a->idx++;
	return 0;
}



static int body_get_len(struct binrpc_pkt* body,
							struct rpc_struct_head* sl_head)
{
	struct rpc_struct_l* l;
	int len;
	
	len=binrpc_pkt_len(body);
	clist_foreach(sl_head, l, next){
		len+=body_get_len(&l->pkt, &l->substructs);
	}
	return len;
}



static int body_fill_iovec(struct iovec_array* v_a,
							struct binrpc_pkt* body, 
							struct rpc_struct_head* sl_head)
{
	int offs;
	struct rpc_struct_l* l;
	int ret;
	
	offs=0;
	clist_foreach(sl_head, l, next){
		if ((ret=append_iovec(v_a, body->body+offs, l->offset-offs))<0)
			goto error;
		offs=l->offset;
		if ((ret=body_fill_iovec(v_a, &l->pkt, &l->substructs))<0)
			goto error;
	};
	/* copy the rest */
	ret=append_iovec(v_a, body->body+offs, binrpc_pkt_len(body)-offs);
error:
	return ret;
}



#if 0
/* expects an initialized new_b */
static int build_structs(struct binrpc_pkt *new_b, struct binrpc_pkt* body, 
							struct rpc_struct_head* sl_head)
{
	int offs;
	struct rpc_struct_l* l;
	int ret;
	
	offs=0;
	clist_foreach(sl_head, l, next){
		if ((ret=append_pkt_body(new_b, body->body+offs, l->offset-offs))<0)
			goto error;
		offs=l->offset;
		if ((ret=build_structs(new_b, &l->pkt, &l->substructs))<0)
			goto error;
	};
	/* copy the rest */
	ret=append_pkt_body(new_b, body->body+offs, binrpc_pkt_len(body)-offs);
error:
	return ret;
}
#endif



static void free_structs(struct rpc_struct_head* sl_head)
{
	struct rpc_struct_l* l;
	struct rpc_struct_l* tmp;
	
	clist_foreach_safe(sl_head, l, tmp, next){
		free_structs(&l->substructs);
		memset(l, 0, sizeof(struct rpc_struct_l)); /* debugging */
		ctl_free(l);
	};
}



inline static int init_binrpc_ctx(	struct binrpc_ctx* ctx,
									unsigned char* recv_buf,
									int recv_buf_len,
									void* send_handle
								)
{
	int err;
	unsigned char* send_buf;
	int send_buf_len;
	
	memset(ctx, 0, sizeof(struct binrpc_ctx));
	clist_init(&ctx->out.structs, next, prev);
	ctx->send_h=send_handle;
	ctx->in.end=recv_buf+recv_buf_len;
	ctx->in.s=binrpc_parse_init(&ctx->in.ctx, recv_buf, recv_buf_len, &err);
	if (err<0) goto end;
	if ((ctx->in.ctx.tlen+(int)(ctx->in.s-recv_buf))>recv_buf_len){
		err=E_BINRPC_MORE_DATA;
		goto end;
	}
	/* fix end value */
	ctx->in.end=ctx->in.s+ctx->in.ctx.tlen;
	
	/* alloc temporary body buffer */
	send_buf_len=BINRPC_MAX_BODY;
	send_buf=ctl_malloc(send_buf_len);
	if (send_buf==0){
		err=E_BINRPC_LAST;
		goto end;
	}
	/* we'll keep only the body */
	err=binrpc_init_pkt(&ctx->out.pkt, send_buf, send_buf_len);
	if(err!=0) {
		ctl_free(send_buf);
	}
end:
	return err;
}



static inline void destroy_binrpc_ctx(struct binrpc_ctx* ctx)
{
	free_structs(&ctx->out.structs);
	if (ctx->out.pkt.body){
		ctl_free(ctx->out.pkt.body);
		ctx->out.pkt.body=0;
	}
	if (ctx->err_phrase.s){
		ctl_free(ctx->err_phrase.s);
		ctx->err_phrase.s=NULL;
	}
	binrpc_gc_collect(ctx);
}



#define MAX_FAULT_LEN 256
#define FAULT_START_BUF (3 /* maxint*/+2/*max str header*/)
static void _rpc_fault(struct binrpc_ctx* ctx, int code,
			char *phrase, int phrase_len)
{
	static unsigned char fault_start[FAULT_START_BUF];
	static unsigned char hdr[BINRPC_MAX_HDR_SIZE];
	struct iovec v[3];
	struct binrpc_pkt body;
	int b_len;
	int hdr_len;
	int err;

	if (ctx->replied){
		LOG(L_ERR, "ERROR: binrpc: rpc_send: rpc method %s tried to reply"
					" more then once\n", ctx->method?ctx->method:"");
		return;
	}
	err=0;
	err=binrpc_init_pkt(&body, fault_start, FAULT_START_BUF);
	if (err<0){
		LOG(L_ERR, "ERROR: binrpc_init_pkt error\n");
		goto error;
	}
	/* adding a fault "manually" to avoid extra memcpys */
	err=binrpc_addint(&body, code);
	if (err<0){
		LOG(L_ERR, "ERROR: _rpc_fault: addint error\n");
		goto error;
	}
	err=binrpc_add_str_mark(&body, BINRPC_T_STR, phrase_len);
	if (err<0){
		LOG(L_ERR, "ERROR: _rpc_fault: add_str_mark error\n");
		goto error;
	}
	/*
	err=binrpc_addfault(&body, code, phrase, phrase_len);
	if (err<0){
		LOG(L_ERR, "ERROR: binrpc_addfault error\n");
		goto error;
	}*/
	b_len=binrpc_pkt_len(&body);
	err=hdr_len=binrpc_build_hdr(BINRPC_FAULT, b_len+phrase_len,
								ctx->in.ctx.cookie, hdr, BINRPC_MAX_HDR_SIZE);
	if (err<0){
		LOG(L_ERR, "ERROR: binrpc_build_hdr error\n");
		goto error;
	}
	v[0].iov_base=hdr;
	v[0].iov_len=hdr_len;
	v[1].iov_base=body.body;
	v[1].iov_len=b_len;
	v[2].iov_base=phrase;
	v[2].iov_len=phrase_len;
	if ((err=sock_send_v(ctx->send_h, v, 3))<0){
		if (err==-2){
			LOG(L_ERR, "ERROR: _rpc_fault: send failed: "
					"datagram too big\n");
			return;
		}
		LOG(L_ERR, "ERROR: _rpc_fault: send failed\n");
		return;
	}
	ctx->replied=1;
	return;
error:
	LOG(L_ERR, "ERROR: _rpc_fault: binrpc_* failed with: %s (%d)\n",
			binrpc_error(err), err);
}

static void rpc_fault(struct binrpc_ctx* ctx, int code, char* fmt, ...)
{
	char buf[MAX_FAULT_LEN];
	va_list ap;
	int len;

	if (ctx->replied){
		LOG(L_ERR, "ERROR: binrpc: rpc_send: rpc method %s tried to reply"
					" more then once\n", ctx->method?ctx->method:"");
		return;
	}
	va_start(ap, fmt);
	len=vsnprintf(buf, MAX_FAULT_LEN, fmt, ap); /* ignore trunc. errors */
	if ((len<0) || (len > MAX_FAULT_LEN))
		len=MAX_FAULT_LEN-1;
	va_end(ap);

	len++; /* vnsprintf doesn't include the terminating 0 */
	_rpc_fault(ctx, code, buf, len);
}

/* Prepare the error reply without sending out the message */
static int rpc_fault_prepare(struct binrpc_ctx* ctx, int code, char* fmt, ...)
{
	char buf[MAX_FAULT_LEN];
	va_list ap;
	int len;

	if (ctx->replied){
		LOG(L_ERR, "ERROR: binrpc: rpc_send: rpc method %s tried to reply"
					" more then once\n", ctx->method?ctx->method:"");
		return -1;
	}
	va_start(ap, fmt);
	len=vsnprintf(buf, MAX_FAULT_LEN, fmt, ap); /* ignore trunc. errors */
	if ((len<0) || (len >= MAX_FAULT_LEN))
		len=MAX_FAULT_LEN-1;
	va_end(ap);

	len++; /* vnsprintf doesn't include the terminating 0 */

	ctx->err_code = code;
	if (ctx->err_phrase.s)
		ctl_free(ctx->err_phrase.s);
	ctx->err_phrase.s = (char*)ctl_malloc(sizeof(char)*len);
	if (!ctx->err_phrase.s) {
		ctx->err_code = 0;
		ctx->err_phrase.len = 0;
		LOG(L_ERR, "ERROR: rpc_fault_prepare: not enough memory\n");
		return -1;
	}
	memcpy(ctx->err_phrase.s, buf, len);
	ctx->err_phrase.len = len;
	return 0;
}

/* Reset the saved error code */
static void rpc_fault_reset(struct binrpc_ctx* ctx)
{
	ctx->err_code = 0;
	if (ctx->err_phrase.s) {
		ctl_free(ctx->err_phrase.s);
		ctx->err_phrase.s = NULL;
		ctx->err_phrase.len = 0;
	}
}

/* wrapper around sock_send_v for staggered buffer writing */
static int rpc_send_v(struct iovec_array *a)
{
	int ret;

	if (a->idx <= 0)
		return 0;

	ret = sock_send_v(a->ctx, a->v, a->idx);
	if (ret < 0)
		return ret;

	a->idx = 0;
	return 0;
}

/* build the reply from the current body */
static int rpc_send(struct binrpc_ctx* ctx)
{
	int b_len;
	int hdr_len;
	struct iovec v[MAX_MSG_CHUNKS];
	struct iovec_array a;
	static unsigned char hdr[BINRPC_MAX_HDR_SIZE];
	int err;
	
	err=0;
	a.v=v;
	a.idx=1;
	a.len=MAX_MSG_CHUNKS;
	a.ctx = ctx->send_h;
	
	if (ctx->replied){
		LOG(L_ERR, "ERROR: binrpc: rpc_send: rpc method %s tried to reply"
					" more then once\n", ctx->method?ctx->method:"");
		goto error;
	}
	b_len=body_get_len(&ctx->out.pkt, &ctx->out.structs);
	err=hdr_len=binrpc_build_hdr( BINRPC_REPL, b_len, ctx->in.ctx.cookie,
									hdr, BINRPC_MAX_HDR_SIZE);
	if (err<0){
		LOG(L_ERR, "ERROR: binrpc: rpc_fault: binrpc_* failed with:"
					" %s (%d)\n", binrpc_error(err), err);
		goto error;
	}
	v[0].iov_base=hdr;
	v[0].iov_len=hdr_len;
	/* fill the rest of the iovecs */
	err=body_fill_iovec(&a, &ctx->out.pkt, &ctx->out.structs);
	if (err<0){
		LOG(L_ERR, "ERROR: binrprc: rpc_send: too many message chunks\n");
		goto error;
	}
	if ((err = rpc_send_v(&a)) < 0){
		if (err==-2){
			LOG(L_ERR, "ERROR: binrpc: rpc_send: send failed: "
					"datagram too big\n");
			goto error;
		}
		LOG(L_ERR, "ERROR: binrprc: rpc_send: send failed\n");
		goto error;
	}
	ctx->replied=1;
	return 0;
error:
	return -1;
}



/* params: buf, size     - buffer containing the packet
 *         bytes_needed  - int pointer, filled with how many bytes are still
 *                         needed (after bytes_needed new bytes received this
 *                         function will be called again 
 *         reply,        - buffer where the reply will be written
 *         reply_len     - intially filled with the reply buffer len,
 *                         after the call will contain how much of that 
 *                         buffer was really used
 * returns: number of bytes processed on success/partial success
 *          -1 on error
 */
int process_rpc_req(unsigned char* buf, int size, int* bytes_needed,
					void* sh, void** saved_state)
{
	int err;
	struct binrpc_val val;
	rpc_export_t* rpc_e;
	struct binrpc_ctx f_ctx;
	struct binrpc_parse_ctx* ctx;

	if(ksr_shutdown_phase()) {
		/* during shutdown - no more RPC command handling */
		LM_DBG("shutdown phase - skipping rpc command\n");
		return -1;
	}

	if (size<BINRPC_MIN_PKT_SIZE){
		*bytes_needed=BINRPC_MIN_PKT_SIZE-size;
		LM_DBG("more data needed - at least: %d bytes\n", *bytes_needed);
		return 0; /* more data, nothing processed */
	}
	err=init_binrpc_ctx(&f_ctx, buf, size, sh);
	ctx=&f_ctx.in.ctx;
	if (err<0){
		if (err==E_BINRPC_MORE_DATA){
			if (f_ctx.in.ctx.tlen){
				*bytes_needed=ctx->tlen+(int)(f_ctx.in.s-buf)-size;
			}else{
				*bytes_needed=1; /* we don't really know how much */
			}
			goto more_data;
		}else if( err==E_BINRPC_LAST){
			LOG(L_ERR, "ERROR: init_binrpc_ctx: out of memory\n");
			rpc_fault(&f_ctx, 500, "internal server error: out of mem.");
			goto error;
		}
		rpc_fault(&f_ctx, 400, "bad request: %s", binrpc_error(err));
		goto error;
	}
	err=E_BINRPC_BADPKT;
	if (ctx->type!=BINRPC_REQ){
		rpc_fault(&f_ctx, 400, "bad request: %s", binrpc_error(err));
		goto error;
	}
	/* now we have the entire packet */
	
	/* get rpc method */
	val.type=BINRPC_T_STR;
	f_ctx.in.s=binrpc_read_record(ctx, f_ctx.in.s, f_ctx.in.end, &val, 0, &err);
	if (err<0){
		LOG(L_CRIT, "ERROR: bad rpc request method, binrpc error: %s (%d)\n",
				binrpc_error(err), err);
		rpc_fault(&f_ctx, 400, "bad request method: %s", binrpc_error(err) );
		goto error;
	}
	
	/* find_rpc_exports needs 0 terminated strings, but all str are
	 * 0 term by default */
	rpc_e=find_rpc_export(val.u.strval.s, 0);
	if ((rpc_e==0) || (rpc_e->function==0)){
		rpc_fault(&f_ctx, 500, "command %s not found", val.u.strval.s);
		goto end;
	}
	f_ctx.method=val.u.strval.s;
	rpc_e->function(&binrpc_callbacks, &f_ctx);
	if (f_ctx.replied==0){
		if ((binrpc_pkt_len(&f_ctx.out.pkt)==0)
			&& f_ctx.err_code && f_ctx.err_phrase.s
		) {
			_rpc_fault(&f_ctx, f_ctx.err_code,
				f_ctx.err_phrase.s, f_ctx.err_phrase.len);
		/* to get an error reply if the rpc handlers hasn't replied
		 *  uncomment the following code:
		 * } else if (binrpc_pkt_len(&f_ctx.out.pkt)==0){
			rpc_fault(&f_ctx, 500, "internal server error: no reply");
			LOG(L_ERR, "ERROR: rpc method %s hasn't replied\n",
					val.u.strval.s);
		 */
		} else {
			rpc_send(&f_ctx);
		}
	}
end:
	*bytes_needed=0; /* full read */
	destroy_binrpc_ctx(&f_ctx);
	return (int)(f_ctx.in.end-buf);
error:
	if (f_ctx.replied==0){
			rpc_fault(&f_ctx, 500, "internal server error");
			LOG(L_ERR, "ERROR: unknown rpc error\n");
	}
	*bytes_needed=0; /* we don't need anymore crap */
	destroy_binrpc_ctx(&f_ctx);
	return -1;
more_data:
	destroy_binrpc_ctx(&f_ctx);
	return 0; /* nothing was processed */
}




static char* rpc_type_name(int type)
{
	switch(type){
		case BINRPC_T_INT:
			return "integer";
		case BINRPC_T_STR:
			return "string";
		case BINRPC_T_DOUBLE:
			return "float";
		case BINRPC_T_STRUCT:
			return "structure";
		case BINRPC_T_ARRAY:
			return "array";
		case BINRPC_T_AVP:
			return "structure member";
		case BINRPC_T_BYTES:
			return "bytes array";
		case BINRPC_T_ALL:
			return "any";
	}
	return "<unknown/error>";
};



/** converts a binrpc_val to int.
  *@return int val on success, 0 and sets err on error (E_BINRPC_TYPE) */
inline static int binrpc_val_conv_int( struct binrpc_val* v, int* err)
{
	int ret;
	
	*err=0;
	switch(v->type){
		case BINRPC_T_INT:
			return v->u.intval;
		case BINRPC_T_DOUBLE:
			return (int) v->u.fval;
		case BINRPC_T_STR:
			if (str2sint(&v->u.strval, &ret)==0)
				return ret;
	}
	*err=E_BINRPC_TYPE;
	return 0;
}



/** converts a binrpc_val to double.
  *@return double val on success, 0 and sets err on error (E_BINRPC_TYPE) */
inline static double binrpc_val_conv_double( struct binrpc_val* v, int* err)
{
	double ret;
	char* end;
	
	*err=0;
	switch(v->type){
		case BINRPC_T_DOUBLE:
			return v->u.fval;
		case BINRPC_T_INT:
			return (double)v->u.intval;
		case BINRPC_T_STR:
			ret=strtod(v->u.strval.s, &end);
			if (end!=v->u.strval.s)
				return ret;
	}
	*err=E_BINRPC_TYPE;
	return 0;
}



/** converts a binrpc_val to str.
  *@return str val pointer on success, 0 and sets err on error (E_BINRPC_TYPE)*/
inline static str* binrpc_val_conv_str(struct binrpc_ctx* ctx,
										struct binrpc_val* v, int* err)
{
	str* ret;
	char* s;
	int len;
	
	*err=0;
	switch(v->type){
		case BINRPC_T_STR:
			return &v->u.strval;
		case BINRPC_T_INT:
			s=int2str(v->u.intval, &len);
			ret=ctl_malloc(sizeof(*ret)+len+1);
			if (ret==0 || binrpc_gc_track(ctx, ret)!=0){
				if(ret!=0) ctl_free(ret);
				*err=E_BINRPC_OVERFLOW;
				return 0;
			}
			ret->s=(char*)ret+sizeof(*ret);
			ret->len=len;
			memcpy(ret->s, s, len);
			ret->s[len]=0;
			return ret;
		case BINRPC_T_DOUBLE:
			/* for now the double to string conversion is not supported*/
			*err=E_BINRPC_BUG;
			return 0;
	}
	*err=E_BINRPC_TYPE;
	return 0;
}



/* rpc interface functions */

/* returns the number of parameters read
 * on error: - number of parameters read so far (<=0)*/
static int rpc_scan(struct binrpc_ctx* ctx, char* fmt, ...)
{
	va_list ap;
	struct binrpc_val v;
	int err;
	char* orig_fmt;
	int nofault;
	int modifiers;
	int autoconv;
	int i;
	double d;
	str* s;
	
	/* clear the previously saved error code */
	rpc_fault_reset(ctx);

	orig_fmt=fmt;
	nofault = 0;
	modifiers=0;
	autoconv=autoconvert;
	va_start(ap, fmt);
	for (;*fmt; fmt++){
		switch(*fmt){
			case '*': /* start of optional parameters */
				nofault = 1;
				modifiers++;
				continue;
			case '.': /* autoconv. on for the next parameter */
				modifiers++;
				autoconv=1;
				continue;
			case 'b': /* bool */
			case 't': /* time */
			case 'd': /* int */
			case 'u': /* uint */
				v.type=autoconv?BINRPC_T_ALL:BINRPC_T_INT;
				ctx->in.s=binrpc_read_record(&ctx->in.ctx, ctx->in.s,
												ctx->in.end, &v, 0, &err);
				if (err<0 || ((i=binrpc_val_conv_int(&v, &err))==0 && err<0))
						goto error_read;
				*(va_arg(ap, int*))=i;
				break;
			case 'f':
				v.type=autoconv?BINRPC_T_ALL:BINRPC_T_DOUBLE;
				ctx->in.s=binrpc_read_record(&ctx->in.ctx, ctx->in.s,
												ctx->in.end, &v, 0, &err);
				if (err<0 || ((d=binrpc_val_conv_double(&v, &err))==0 &&
						err<0))
					goto error_read;
				*(va_arg(ap, double*))=d;
				break;
			case 'l':
				/* long - convert it from double */
				v.type=autoconv?BINRPC_T_ALL:BINRPC_T_DOUBLE;
				ctx->in.s=binrpc_read_record(&ctx->in.ctx, ctx->in.s,
												ctx->in.end, &v, 0, &err);
				if (err<0 || ((d=binrpc_val_conv_double(&v, &err))==0 &&
						err<0))
					goto error_read;
				*(va_arg(ap, long*))=(long)d;
				break;
			case 'j':
				/* unsigned long - convert it from double */
				v.type=autoconv?BINRPC_T_ALL:BINRPC_T_DOUBLE;
				ctx->in.s=binrpc_read_record(&ctx->in.ctx, ctx->in.s,
												ctx->in.end, &v, 0, &err);
				if (err<0 || ((d=binrpc_val_conv_double(&v, &err))==0 &&
						err<0))
					goto error_read;
				*(va_arg(ap, unsigned long*))=(unsigned long)d;
				break;
			case 'L':
				/* long long - convert it from double */
				v.type=autoconv?BINRPC_T_ALL:BINRPC_T_DOUBLE;
				ctx->in.s=binrpc_read_record(&ctx->in.ctx, ctx->in.s,
												ctx->in.end, &v, 0, &err);
				if (err<0 || ((d=binrpc_val_conv_double(&v, &err))==0 &&
						err<0))
					goto error_read;
				*(va_arg(ap, long long*))=(long long)d;
				break;
			case 'J':
				/* unsigned long long - convert it from double */
				v.type=autoconv?BINRPC_T_ALL:BINRPC_T_DOUBLE;
				ctx->in.s=binrpc_read_record(&ctx->in.ctx, ctx->in.s,
												ctx->in.end, &v, 0, &err);
				if (err<0 || ((d=binrpc_val_conv_double(&v, &err))==0 &&
						err<0))
					goto error_read;
				*(va_arg(ap, unsigned long long*))=(unsigned long long)d;
				break;
			case 's': /* asciiz */
			case 'S': /* str */
				v.type=autoconv?BINRPC_T_ALL:BINRPC_T_STR;
				ctx->in.s=binrpc_read_record(&ctx->in.ctx, ctx->in.s, 
												ctx->in.end, &v, 0, &err);
				if (err<0 || ((s=binrpc_val_conv_str(ctx, &v, &err))==0 ||
							err<0)){
					v.u.strval.s="if you get this string, you don't"
								"check rpc_scan return code !!! (very bad)";
					v.u.strval.len=strlen(v.u.strval.s);
					s=&v.u.strval;
				}
				if (*fmt=='s'){
					*(va_arg(ap, char**))=s->s; /* 0 term by proto*/
				}else{
					*(va_arg(ap, str*))=*s;
				}
				if (err<0) goto error_read;
				break;
			case '{': /* struct */
				v.type=BINRPC_T_STRUCT;
				/* FIXME: structure reading doesn't work for now */
#if 0
				ctx->in.s=binrpc_read_record(&ctx->in.ctx, ctx->in.s, 
												ctx->in.end, &v, 0, &err);
				if (err<0) goto error_read;
				ctx->in.in_struct++;
				*(va_arg(ap, void**))=ctx; /* use the same context */
#endif
				goto error_not_supported;
				break;
			default:
				goto error_inv_param;
		}
		autoconv=autoconvert; /* reset autoconv*/
		ctx->in.record_no++;
	}
	va_end(ap);
	return (int)(fmt-orig_fmt)-modifiers;
error_read:
	/* Do not immediately send out the error message, the user might retry the scan with
	different parameters */
	if(nofault==0 || ((err!=E_BINRPC_MORE_DATA) && (err!=E_BINRPC_EOP))) {
		rpc_fault_prepare(ctx, 400, "error at parameter %d: expected %s type but"
						" %s", ctx->in.record_no, rpc_type_name(v.type),
						 binrpc_error(err));
	/*
	rpc_fault(ctx, 400, "invalid record %d, offset %d (expected %d type)"
						": %s", ctx->in.record_no, ctx->in.ctx.offset,
								v.type, binrpc_error(err));
	*/
	}
	if(nofault==1 && (err==E_BINRPC_MORE_DATA || err==E_BINRPC_EOP)) {
		va_end(ap);
		return (int)(fmt-orig_fmt)-modifiers;
	}
	goto error_ret;
error_not_supported:
	rpc_fault(ctx, 500, "internal server error, type %d not supported",
						v.type);
	LOG(L_CRIT, "BUG: binrpc: rpc_scan: formatting char \'%c\'"
						" not supported\n", *fmt);
goto error_ret;
error_inv_param:
	rpc_fault(ctx, 500, "internal server error, invalid format char \'%c\'",
						*fmt);
error_ret:
	va_end(ap);
	return -((int)(fmt-orig_fmt)-modifiers);
}



/* returns  0 on success, -1 on error */
static int rpc_add(struct binrpc_ctx* ctx, char* fmt, ...)
{
	va_list ap;
	int err;
	str st;
	str* sp;
	struct rpc_struct_l* rs;
	str null_value = str_init("<null string>");
	double d;

	va_start(ap, fmt);
	for (;*fmt; fmt++){
		switch(*fmt){
			case 'd':
			case 't':
			case 'b':
			case 'u':
				err=binrpc_addint(&ctx->out.pkt, va_arg(ap, int));
				if (err<0) goto error_add;
				break;
			case 's': /* asciiz */
				st.s=va_arg(ap, char*);
				if (st.s==0) {
					/* fix null strings */
					st=null_value;
				} else {
					st.len=strlen(st.s);
				}
				err=binrpc_addstr(&ctx->out.pkt, st.s, st.len);
				if (err<0) goto error_add;
				break;
			case 'S': /* str */
				sp=va_arg(ap, str*);
				if(sp!=NULL && sp->s!=NULL) {
					st=*sp;
				} else {
					st=null_value;
				}
				err=binrpc_addstr(&ctx->out.pkt, st.s, st.len);
				if (err<0) goto error_add;
				break;
			case '{':
			case '[':
				err=binrpc_start_struct(&ctx->out.pkt);
				if (err<0) goto error_add;
				rs=new_rpc_struct();
				if (rs==0) goto error_mem;
				rs->offset=binrpc_pkt_len(&ctx->out.pkt);
				err=binrpc_end_struct(&ctx->out.pkt);
				if (err<0) goto error_add;
				clist_append(&ctx->out.structs, rs, next, prev);
				*(va_arg(ap, void**))=rs;
				break;
			case 'f':
				err=binrpc_adddouble(&ctx->out.pkt, va_arg(ap, double));
				if (err<0) goto error_add;
				break;
			case 'l':
				/* long - store in a double */
				d =  (double)va_arg(ap, long);
				err=binrpc_adddouble(&ctx->out.pkt, d);
				if (err<0) goto error_add;
				break;
			case 'j':
				/* unsigned long - store in a double */
				d =  (double)va_arg(ap, unsigned long);
				err=binrpc_adddouble(&ctx->out.pkt, d);
				if (err<0) goto error_add;
				break;
			case 'L':
				/* long long - store in a double */
				d =  (double)va_arg(ap, long long);
				err=binrpc_adddouble(&ctx->out.pkt, d);
				if (err<0) goto error_add;
				break;
			case 'J':
				/* unsigned long long - store in a double */
				d =  (double)va_arg(ap, unsigned long long);
				err=binrpc_adddouble(&ctx->out.pkt, d);
				if (err<0) goto error_add;
				break;
			default:
				rpc_fault(ctx, 500, "Internal server error: "
								"invalid formatting character \'%c\'", *fmt);
				LOG(L_CRIT, "BUG: binrpc: rpc_add: formatting char \'%c\'"
							" not supported\n", *fmt);
				goto error;
		}
	}
	va_end(ap);
	return 0;
error_mem:
	LOG(L_ERR, "ERROR: binrpc: rpc_add: out of memory\n");
	rpc_fault(ctx, 500, "Internal server error: out of memory");
	goto error;
error_add:
	rpc_fault(ctx, 500, "Internal server error processing \'%c\': %s (%d)",
				*fmt, binrpc_error(err), err);
error:
	va_end(ap);
	return -1;
}



#define RPC_PRINTF_BUF_SIZE	binrpc_buffer_size
/* returns  0 on success, -1 on error */
static int rpc_rpl_printf(struct binrpc_ctx* ctx, char* fmt, ...)
{
	va_list ap;
	char* buf;
	int len;
	int err;
	
	buf=ctl_malloc(RPC_PRINTF_BUF_SIZE);
	if (buf==0) goto error;
	va_start(ap, fmt);
	len=vsnprintf(buf, RPC_PRINTF_BUF_SIZE, fmt, ap);
	va_end(ap);
	if ((len<0) || (len> RPC_PRINTF_BUF_SIZE)){
		LOG(L_ERR, "ERROR: binrpc: rpc_rpl_printf: buffer size exceeded(%d)\n",
				RPC_PRINTF_BUF_SIZE);
		goto error;
	}
	if ((err=binrpc_addstr(&ctx->out.pkt, buf, len))<0){
		LOG(L_ERR, "ERROR: binrpc: rpc_rpl_printf: binrpc_addstr failed:"
					" %s (%d)\n", binrpc_error(err), err);
		goto error;
	}
	ctl_free(buf);
	return 0;
error:
	if (buf) ctl_free(buf);
	return -1;
}



/* returns  0 on success, -1 on error */
static int rpc_struct_add(struct rpc_struct_l* s, char* fmt, ...)
{
	va_list ap;
	int err;
	struct binrpc_val avp;
	struct rpc_struct_l* rs;
	str *sp;
	str null_value = str_init("<null string>");

	va_start(ap, fmt);
	for (;*fmt; fmt++){
		memset(&avp, 0, sizeof(struct binrpc_val));
		avp.name.s=va_arg(ap, char*);
		if (avp.name.s)
			avp.name.len=strlen(avp.name.s);
		switch(*fmt){
			case 'd':
			case 't':
			case 'b':
			case 'u':
				avp.type=BINRPC_T_INT;
				avp.u.intval=va_arg(ap, int);
				break;
			case 's': /* asciiz */
				avp.type=BINRPC_T_STR;
				avp.u.strval.s=va_arg(ap, char*);
				if (avp.u.strval.s==NULL) {
					/* fix null strings */
					avp.u.strval=null_value;
				} else {
					avp.u.strval.len=strlen(avp.u.strval.s);
				}
				break;
			case 'S': /* str */
				avp.type=BINRPC_T_STR;
				sp = va_arg(ap, str*);
				if(sp!=NULL && sp->s!=NULL) {
					avp.u.strval=*sp;
				} else {
					avp.u.strval=null_value;
				}
				break;
			case '{':
			case '[':
				avp.type=BINRPC_T_STRUCT;
				err=binrpc_addavp(&s->pkt, &avp);
				if (err<0){
					LM_ERR("failed to add attribute-value (%c)\n", *fmt);
					goto error_add;
				}
				rs=new_rpc_struct();
				if (rs==0){
					LM_ERR("not enough memory (%c)\n", *fmt);
					goto error_mem;
				}
				rs->offset=binrpc_pkt_len(&s->pkt);
				err=binrpc_end_struct(&s->pkt);
				if (err<0) {
					LM_ERR("failed to end struct (%c)\n", *fmt);
					goto error_add;
				}
				clist_append(&s->substructs, rs, next, prev);
				*(va_arg(ap, void**))=rs;
				goto end;
			case 'f':
				avp.type=BINRPC_T_DOUBLE;
				avp.u.fval=va_arg(ap, double);
				break;
			case 'l':
				/* long - store in a double */
				avp.type=BINRPC_T_DOUBLE;
				avp.u.fval=(double)va_arg(ap, long);
				break;
			case 'j':
				/* unsigned long - store in a double */
				avp.type=BINRPC_T_DOUBLE;
				avp.u.fval=(double)va_arg(ap, unsigned long);
				break;
			case 'L':
				/* long long - store in a double */
				avp.type=BINRPC_T_DOUBLE;
				avp.u.fval=(double)va_arg(ap, long long);
				break;
			case 'J':
				/* unsigned long long - store in a double */
				avp.type=BINRPC_T_DOUBLE;
				avp.u.fval=(double)va_arg(ap, unsigned long long);
				break;
			default:
				LM_ERR("formatting char \'%c\' not supported\n", *fmt);
				goto error;
		}
		err=binrpc_addavp(&s->pkt, &avp);
		if (err<0) {
			LM_ERR("failed to add attribute-value (%c)\n", *fmt);
			goto error;
		}
	}
end:
	va_end(ap);
	return 0;
error_mem:
error_add:
error:
	va_end(ap);
	return -1;
}

/* returns  0 on success, -1 on error */
static int rpc_array_add(struct rpc_struct_l* s, char* fmt, ...)
{
	va_list ap;
	int err;
	str st;
	str *sp;
	struct rpc_struct_l* rs;
	str null_value = str_init("<null string>");
	double d;

	va_start(ap, fmt);
	for (;*fmt; fmt++){
		switch(*fmt){
			case 'd':
			case 't':
			case 'b':
			case 'u':
				err=binrpc_addint(&s->pkt, va_arg(ap, int));
				if (err<0) goto error_add;
				break;
			case 's': /* asciiz */
				st.s=va_arg(ap, char*);
				if (st.s==0) {
					/* fix null strings */
					st=null_value;
				} else {
					st.len = strlen(st.s);
				}
				err=binrpc_addstr(&s->pkt, st.s, st.len);
				if (err<0) goto error_add;
				break;
			case 'S': /* str */
				sp=va_arg(ap, str*);
				if(sp!=NULL && sp->s!=NULL) {
					st=*sp;
				} else {
					st=null_value;
				}
				err=binrpc_addstr(&s->pkt, st.s, st.len);
				if (err<0) goto error_add;
				break;
			case '{':
			case '[':
				err=binrpc_start_struct(&s->pkt);
				if (err<0) goto error_add;
				rs=new_rpc_struct();
				if (rs==0) goto error_mem;
				rs->offset=binrpc_pkt_len(&s->pkt);
				err=binrpc_end_struct(&s->pkt);
				if (err<0) goto error_add;
				clist_append(&s->substructs, rs, next, prev);
				*(va_arg(ap, void**))=rs;
				break;
			case 'f':
				err=binrpc_adddouble(&s->pkt, va_arg(ap, double));
				if (err<0) goto error_add;
				break;
			case 'l':
				/* long - store in a double */
				d = (double)va_arg(ap, long);
				err=binrpc_adddouble(&s->pkt, d);
				if (err<0) goto error_add;
				break;
			case 'j':
				/* unsigned long - store in a double */
				d = (double)va_arg(ap, unsigned long);
				err=binrpc_adddouble(&s->pkt, d);
				if (err<0) goto error_add;
				break;
			case 'L':
				/* long long - store in a double */
				d = (double)va_arg(ap, long long);
				err=binrpc_adddouble(&s->pkt, d);
				if (err<0) goto error_add;
				break;
			case 'J':
				/* unsigned long long - store in a double */
				d = (double)va_arg(ap, unsigned long long);
				err=binrpc_adddouble(&s->pkt, d);
				if (err<0) goto error_add;
				break;
			default:
				LOG(L_CRIT, "BUG: binrpc: rpc_add: formatting char \'%c\'"
							" not supported\n", *fmt);
				goto error;
		}
	}
	va_end(ap);
	return 0;
error_mem:
error_add:
error:
	va_end(ap);
	return -1;
}

/* returns  0 on success, -1 on error */
static int rpc_struct_printf(struct rpc_struct_l *s, char* name,
								char* fmt, ...)
{
	va_list ap;
	char* buf;
	int len;
	int err;
	struct binrpc_val avp;
	
	buf=ctl_malloc(RPC_PRINTF_BUF_SIZE);
	if (buf==0) goto error;
	va_start(ap, fmt);
	len=vsnprintf(buf, RPC_PRINTF_BUF_SIZE, fmt, ap);
	va_end(ap);
	if ((len<0) || (len> RPC_PRINTF_BUF_SIZE)){
		LOG(L_ERR, "ERROR: binrpc: rpc_struct_printf:"
				" buffer size exceeded(%d)\n", RPC_PRINTF_BUF_SIZE);
		goto error;
	}
	avp.name.s=name;
	avp.name.len=strlen(name);
	avp.type=BINRPC_T_STR;
	avp.u.strval.s=buf;
	avp.u.strval.len=strlen(buf);
	
	if ((err=binrpc_addavp(&s->pkt, &avp))<0){
		LOG(L_ERR, "ERROR: binrpc: rpc_printf: binrpc_addavp failed:"
					" %s (%d)\n", binrpc_error(err), err);
		goto error;
	}
	ctl_free(buf);
	return 0;
error:
	if (buf) ctl_free(buf);
	return -1;
}



static int rpc_struct_scan(struct rpc_struct_l* s, char* fmt, ...)
{
	LOG(L_CRIT, "ERROR: binrpc:rpc_struct_scan: not implemented\n");
	return -1;
};
	
