/**
 * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
 *
 * Author: Seudin Kasumovic (seudin.kasumovic@gmail.com)
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * 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 <stdarg.h>
#include <stdlib.h>

#include "../../core/mem/mem.h"
#include "../../core/dprint.h"

#include "handle_rpc.h"
#include "erlang_mod.h"

/* create empty recycle bin */
static struct erl_rpc_garbage *recycle_bin = 0;

static int get_int(
		int *int_ptr, erl_rpc_ctx_t *ctx, int reads, int autoconvert);
static int get_double(
		double *double_prt, erl_rpc_ctx_t *ctx, int reads, int autoconvert);
static int get_str(
		str *str_ptr, erl_rpc_ctx_t *ctx, int reads, int autoconvert);
static int find_member(erl_rpc_ctx_t *ctx, int arity, const char *member_name);
static int add_to_recycle_bin(int type, void *ptr, erl_rpc_ctx_t *ctx);

erl_rpc_param_t *erl_new_param(erl_rpc_ctx_t *ctx);
void erl_rpc_append_param(erl_rpc_ctx_t *ctx, erl_rpc_param_t *param);

/*
 * RPC holder
 */
rpc_t erl_rpc_func_param;

/*
 * Function returns always success - we uses EPMD for transport
 */
int erl_rpc_send(erl_rpc_ctx_t *ctx, int depth)
{
	if(ctx->response_sent)
		return 0;
	ctx->response_sent = 1;
	erl_rpc_ctx_t *handler;
	erl_rpc_param_t *fault = *(ctx->fault_p);

	if(fault) {
		LM_ERR("fault: %d %.*s\n", fault->type, STR_FMT(&fault->value.S));
		/* restore clear point */
		ctx->response->index = ctx->response_index;

		/* {error,{struct,[ {"code", 400}, {"error","Error message"}]}}*/
		if(ei_x_encode_tuple_header(ctx->response, 1))
			goto error; /* {error,{_,_}} */
		if(rpc_reply_with_struct && ei_x_encode_atom(ctx->response, "struct"))
			goto error; /* {error,{struct,_}} */
		if(ei_x_encode_list_header(ctx->response, 2))
			goto error; /* {error,{struct,[_,_]}} */
		if(ei_x_encode_tuple_header(ctx->response, 2))
			goto error; /* {error,{struct,[{_,_},_]}} */
		if(ei_x_encode_atom(ctx->response, "code"))
			goto error; /* {error,{struct,[{code,_},_]}} */
		if(ei_x_encode_long(ctx->response, fault->type))
			goto error; /* {error,{struct,[{code,400},_]}} */
		if(ei_x_encode_tuple_header(ctx->response, 2))
			goto error; /* {error,{struct,[{code,400},{_,_}]}} */
		if(ei_x_encode_binary(ctx->response, "error", sizeof("error") - 1))
			goto error; /* {error,{struct,[{code,400},{<<"error">>,_}]}} */
		if(ei_x_encode_binary(ctx->response, (void *)fault->value.S.s,
				   fault->value.S
						   .len)) /* {error,{struct,[{code,400},{<<"error">>,<<Msg>>}]}} */
			goto error;
		if(ei_x_encode_empty_list(ctx->response))
			goto error;
	} else if(ctx->reply_params) {
		while(ctx->reply_params) {
			if(ctx->reply_params->member_name) {
				/* {"member_name", _} */
				if(ei_x_encode_tuple_header(ctx->response, 2))
					goto error;
				if(ei_x_encode_binary(ctx->response,
						   ctx->reply_params->member_name,
						   strlen(ctx->reply_params->member_name)))
					goto error;
			}
			/* {"member_name", MemberValue} */
			switch(ctx->reply_params->type) {
				case ERL_INTEGER_EXT:
					if(ei_x_encode_long(
							   ctx->response, ctx->reply_params->value.n))
						goto error;
					break;
				case ERL_FLOAT_EXT:
					if(ei_x_encode_double(
							   ctx->response, ctx->reply_params->value.d))
						goto error;
					break;
				case ERL_STRING_EXT:
					if(ei_x_encode_binary(ctx->response,
							   ctx->reply_params->value.S.s,
							   ctx->reply_params->value.S.len))
						goto error;
					break;
				case ERL_SMALL_TUPLE_EXT: /* add as {struct,list(no_params)} */
					handler = (erl_rpc_ctx_t *)ctx->reply_params->value.handler;
					if(ei_x_encode_tuple_header(ctx->response, 1))
						goto error;
					if(rpc_reply_with_struct
							&& ei_x_encode_atom(ctx->response, "struct"))
						goto error;
					if(ei_x_encode_list_header(
							   ctx->response, handler->no_params))
						goto error;
					if(erl_rpc_send(handler, depth++))
						goto error;
					if(ei_x_encode_empty_list(ctx->response))
						goto error;
					break;
				case ERL_LIST_EXT: /* add as [list(no_params)] */
					handler = (erl_rpc_ctx_t *)ctx->reply_params->value.handler;
					if(ei_x_encode_list_header(
							   ctx->response, handler->no_params))
						goto error;
					if(erl_rpc_send(handler, depth++))
						goto error;
					if(handler->no_params)
						if(ei_x_encode_empty_list(ctx->response))
							goto error;
					break;
				default:
					LM_ERR("Unknown type '%c' for encoding RPC reply\n",
							ctx->reply_params->type);
					break;
			}
			ctx->reply_params = ctx->reply_params->next;
		}
	} else if(!depth) {
		/* restore start point */
		LM_WARN("encode empty response -> ok");
		ctx->response->index = ctx->response_index;
		if(ei_x_encode_atom(ctx->response, "ok"))
			goto error;
	}

	return 0;

error:
	LM_ERR("error while encoding response\n");
	return -1;
}

void erl_rpc_fault(erl_rpc_ctx_t *ctx, int code, char *fmt, ...)
{
	static char buf[FAULT_BUF_LEN];
	erl_rpc_param_t *fault = *(ctx->fault_p);
	int len;

	va_list ap;

	if(fault)
		return;

	va_start(ap, fmt);
	len = vsnprintf(buf, FAULT_BUF_LEN, fmt, ap);
	va_end(ap);

	fault = (erl_rpc_param_t *)pkg_malloc(sizeof(erl_rpc_param_t));

	if(fault == 0) {
		LM_ERR("Not enough memory\n");
		return;
	}

	if(add_to_recycle_bin(JUNK_PKGCHAR, (void *)fault, ctx)) {
		pkg_free(fault);
		return;
	}

	fault->type = code;
	fault->value.S.s = buf;
	fault->value.S.len = len;
	ctx->fault = fault;
}

int erl_rpc_add(erl_rpc_ctx_t *ctx, char *fmt, ...)
{
	void **void_ptr;
	int int_ptr;
	double double_ptr;
	char *char_ptr;
	str *str_ptr;
	erl_rpc_ctx_t *handler;
	erl_rpc_param_t *param;

	int reads = 0;

	va_list ap;

	va_start(ap, fmt);

	while(*fmt) {
		if((param = erl_new_param(ctx)) == 0) {
			goto error;
		}

		switch(*fmt) {
			case 'b': /* Bool */
			case 't': /* Date and time */
			case 'd': /* Integer */
				int_ptr = va_arg(ap, int);
				param->type = ERL_INTEGER_EXT;
				param->value.n = int_ptr;
				break;

			case 'f': /* double */
				double_ptr = va_arg(ap, double);
				param->type = ERL_FLOAT_EXT;
				param->value.d = double_ptr;
				break;

			case 'S': /* str structure */
				str_ptr = va_arg(ap, str *);

				param->type = ERL_STRING_EXT;
				param->value.S = *str_ptr;

				break;

			case 's': /* zero terminated string */

				char_ptr = va_arg(ap, char *);

				param->type = ERL_STRING_EXT;
				param->value.S.len = strlen(char_ptr);

				param->value.S.s = (char *)pkg_malloc(param->value.S.len + 1);

				if(!param->value.S.s) {
					LM_ERR("Not enough memory\n");
					goto error;
				}

				if(add_to_recycle_bin(JUNK_PKGCHAR, param->value.S.s, ctx)) {
					pkg_free(param->value.S.s);
					goto error;
				}

				memcpy(param->value.S.s, char_ptr, param->value.S.len);

				break;
			case '{':
				void_ptr = va_arg(ap, void **);

				handler = (erl_rpc_ctx_t *)pkg_malloc(sizeof(erl_rpc_ctx_t));

				if(!handler) {
					LM_ERR("Not enough memory\n");
					goto error;
				}

				if(add_to_recycle_bin(JUNK_PKGCHAR, (void *)handler, ctx)) {
					pkg_free(handler);
					goto error;
				}

				*handler = *ctx;
				handler->no_params = 0;
				handler->reply_params = 0;
				handler->tail = 0;

				/* go where we stopped */
				*(erl_rpc_ctx_t **)void_ptr = handler;

				param->type = ERL_SMALL_TUPLE_EXT;
				param->value.handler = (void *)handler;

				break;

			case '[':
				void_ptr = va_arg(ap, void **);

				handler = (erl_rpc_ctx_t *)pkg_malloc(sizeof(erl_rpc_ctx_t));

				if(!handler) {
					LM_ERR("Not enough memory\n");
					goto error;
				}

				if(add_to_recycle_bin(JUNK_PKGCHAR, (void *)handler, ctx)) {
					pkg_free(handler);
					goto error;
				}

				*handler = *ctx;
				handler->no_params = 0;
				handler->reply_params = 0;
				handler->tail = 0;

				/* go where we stopped */
				*(erl_rpc_ctx_t **)void_ptr = handler;

				param->type = ERL_LIST_EXT;
				param->value.handler = (void *)handler;

				break;
			default:
				LM_ERR("Invalid type '%c' in formatting string\n", *fmt);
				goto error;
		}

		erl_rpc_append_param(ctx, param);

		reads++;
		fmt++;
	}
	va_end(ap);
	return reads;

error:
	LM_ERR("Failed to encode parameter #%d into response.\n", reads);
	va_end(ap);
	return -reads;
}

int erl_rpc_scan(erl_rpc_ctx_t *ctx, char *fmt, ...)
{
	int *int_ptr;
	char **char_ptr;
	str *str_ptr;
	double *double_ptr;
	void **void_ptr;
	str s; /* helper str */

	int reads = 0;
	int modifiers = 0;
	int autoconv = 0;

	int type, size;
	erl_rpc_ctx_t *handler;

	va_list ap;

	va_start(ap, fmt);

	while(*fmt && ctx->size) {
		/* optional and we at the end of decoding params */
		if(ctx->optional && !ctx->size) {
			break;
		}

		if(ei_get_type(ctx->request->buff, &ctx->request_index, &type, &size)) {
			erl_rpc_fault(ctx, 400,
					"Can't determine data type, for parameter #%d", reads);
			LM_ERR("Can't determine data type, for parameter #%d", reads);

			goto error;
		}

		switch(*fmt) {
			case '*': /* start of optional parameters */
				modifiers++;
				ctx->optional = 1;
				reads++;
				fmt++;
				continue;
			case '.': /* autoconvert */
				modifiers++;
				autoconv = 1;
				reads++;
				fmt++;
				continue;
			case 'b': /* Bool */
			case 't': /* Date and time */
			case 'd': /* Integer */
				int_ptr = va_arg(ap, int *);

				if(get_int(int_ptr, ctx, reads, autoconv)) {
					goto error;
				}

				break;
			case 'f': /* double */
				double_ptr = va_arg(ap, double *);

				if(get_double(double_ptr, ctx, reads, autoconv)) {
					goto error;
				}

				break;
			case 'S': /* str structure */

				str_ptr = va_arg(ap, str *);

				if(get_str(str_ptr, ctx, reads, autoconv)) {
					goto error;
				}

				break;
			case 's': /* zero terminated string */

				char_ptr = va_arg(ap, char **);
				if(get_str(&s, ctx, reads, autoconv)) {
					goto error;
				}

				*char_ptr = s.s;

				break;
			case '{':
				void_ptr = va_arg(ap, void **);

				if(type != ERL_SMALL_TUPLE_EXT && type != ERL_LARGE_TUPLE_EXT) {
					erl_rpc_fault(ctx, 400, "Bad type of parameter #%d (t=%c).",
							reads, type);
					goto error;
				}

				handler = (erl_rpc_ctx_t *)pkg_malloc(sizeof(erl_rpc_ctx_t));

				if(!handler) {
					erl_rpc_fault(
							ctx, 500, "Internal Server Error (No memory left)");
					LM_ERR("Not enough memory\n");
					goto error;
				}

				*handler = *ctx; /* copy state */
				handler->optional = 0;
				handler->no_params = 0;
				handler->size = size; /* size of tuple */

				if(add_to_recycle_bin(JUNK_PKGCHAR, handler, ctx)) {
					pkg_free(handler);
					goto error;
				}

				/* skip element */
				if(ei_skip_term(ctx->request->buff, &ctx->request_index)) {
					goto error;
				}

				/* go where we stopped */
				*(erl_rpc_ctx_t **)void_ptr = handler;

				break;
			default:
				LM_ERR("Invalid parameter type in formatting string: %c\n",
						*fmt);
				erl_rpc_fault(ctx, 500,
						"Server Internal Error (Invalid Formatting String)");
				goto error;
		}

		autoconv = 0; /* reset autovoncersion for next parameter */
		reads++;
		fmt++;
		ctx->size--;
	}
	va_end(ap);
	return reads - modifiers;

error:
	va_end(ap);
	return -(reads - modifiers);
}

int erl_rpc_struct_scan(erl_rpc_ctx_t *ctx, char *fmt, ...)
{
	int *int_ptr;
	char **char_ptr;
	str *str_ptr;
	double *double_ptr;
	char *member_name;
	str s; /* helper str */

	int reads = 0;
	int modifiers = 0;
	int index;
	int autoconv = 0;

	int arity;

	va_list ap;

	/* preserve index */
	index = ctx->request_index;

	if(ei_decode_tuple_header(ctx->request->buff, &index, &arity)) {
		erl_rpc_fault(ctx, 400, "Bad tuple");
		return -1;
	}

	va_start(ap, fmt);

	while(*fmt) {
		member_name = va_arg(ap, char *);

		if(find_member(ctx, arity, member_name)) {
			goto error;
		}

		switch(*fmt) {
			case 'b': /* Bool */
			case 't': /* Date and time */
			case 'd': /* Integer */
				int_ptr = va_arg(ap, int *);

				if(get_int(int_ptr, ctx, reads, autoconv)) {
					goto error;
				}

				break;
			case 'f': /* double */
				double_ptr = va_arg(ap, double *);

				if(get_double(double_ptr, ctx, reads, autoconv)) {
					goto error;
				}

				break;
			case 'S': /* str structure */

				str_ptr = va_arg(ap, str *);

				if(get_str(str_ptr, ctx, reads, autoconv)) {
					goto error;
				}

				break;
			case 's': /* zero terminated string */

				char_ptr = va_arg(ap, char **);

				if(get_str(&s, ctx, reads, autoconv)) {
					goto error;
				}

				*char_ptr = s.s;

				break;

			default:
				LM_ERR("Invalid parameter type in formatting string: %c\n",
						*fmt);
				erl_rpc_fault(ctx, 500,
						"Server Internal Error (Invalid Formatting String)");
				goto error;
		}

		reads++;
		fmt++;
	}

	va_end(ap);
	return reads - modifiers;

error:
	va_end(ap);
	return -(reads - modifiers);
}

#define RPC_BUF_SIZE 1024

/*
 * adds formatted string into RPC response buffer as Erlang string/list
 */
int erl_rpc_printf(erl_rpc_ctx_t *ctx, char *fmt, ...)
{
	int n, buff_size;
	char *buff = 0;
	va_list ap;
	erl_rpc_param_t *param;

	buff = (char *)pkg_malloc(RPC_BUF_SIZE);
	if(!buff) {
		erl_rpc_fault(ctx, 500, "Internal Server Error (No memory left)");
		ERR("No memory left\n");
		return -1;
	}

	buff_size = RPC_BUF_SIZE;

	while(1) {
		/* Try to print in the allocated space. */
		va_start(ap, fmt);
		n = vsnprintf(buff, buff_size, fmt, ap);
		va_end(ap);
		/* If that worked, return the string. */
		if(n > -1 && n < buff_size) {
			if(add_to_recycle_bin(JUNK_PKGCHAR, (void *)buff, ctx)) {
				goto error;
			} else if((param = erl_new_param(ctx))) {
				param->type = ERL_STRING_EXT;
				param->value.S.s = buff;
				param->value.S.len = n;
				erl_rpc_append_param(ctx, param);
			} else {
				goto error;
			}

			return 0;
		}

		/* Else try again with more space. */
		if(n > -1) {		   /* glibc 2.1 */
			buff_size = n + 1; /* precisely what is needed */
		} else {			   /* glibc 2.0 */
			buff_size *= 2;	   /* twice the old size */
		}
		if((buff = pkg_realloc(buff, buff_size)) == 0) {
			erl_rpc_fault(ctx, 500, "Internal Server Error (No memory left)");
			ERR("No memory left\n");
			goto error;
		}
	}

	return 0;

error:
	if(buff)
		pkg_free(buff);
	return -1;
}

int erl_rpc_struct_add(erl_rpc_ctx_t *ctx, char *fmt, ...)
{
	void **void_ptr;
	char *char_ptr;
	str *str_ptr;
	erl_rpc_ctx_t *handler;
	erl_rpc_param_t *param;

	int reads = 0;

	va_list ap;

	va_start(ap, fmt);

	while(*fmt) {
		if((param = erl_new_param(ctx)) == 0) {
			goto error;
		}

		param->member_name = va_arg(ap, char *);

		switch(*fmt) {
			case 'b': /* Bool */
			case 't': /* Date and time */
			case 'd': /* Integer */
				param->type = ERL_INTEGER_EXT;
				param->value.n = va_arg(ap, int);
				;
				break;

			case 'f': /* double */
				param->type = ERL_FLOAT_EXT;
				param->value.d = va_arg(ap, double);
				break;

			case 'S': /* str structure */
				str_ptr = va_arg(ap, str *);

				param->type = ERL_STRING_EXT;
				param->value.S = *str_ptr;
				break;

			case 's': /* zero terminated string */

				char_ptr = va_arg(ap, char *);

				param->type = ERL_STRING_EXT;
				param->value.S.len = strlen(char_ptr);

				param->value.S.s = (char *)pkg_malloc(param->value.S.len + 1);

				if(!param->value.S.s) {
					LM_ERR("Not enough memory\n");
					goto error;
				}

				if(add_to_recycle_bin(JUNK_PKGCHAR, param->value.S.s, ctx)) {
					pkg_free(param->value.S.s);
					goto error;
				}

				memcpy(param->value.S.s, char_ptr, param->value.S.len);

				break;

			case '{':
				void_ptr = va_arg(ap, void **);

				handler = (erl_rpc_ctx_t *)pkg_malloc(sizeof(erl_rpc_ctx_t));

				if(!handler) {
					LM_ERR("Not enough memory\n");
					goto error;
				}

				if(add_to_recycle_bin(JUNK_PKGCHAR, (void *)handler, ctx)) {
					pkg_free(handler);
					goto error;
				}

				*handler = *ctx;
				handler->no_params = 0;
				handler->reply_params = 0;
				handler->tail = 0;

				/* go where we stopped */
				*(erl_rpc_ctx_t **)void_ptr = handler;

				param->type = ERL_SMALL_TUPLE_EXT;
				param->value.handler = (void *)handler;

				break;

			case '[':
				void_ptr = va_arg(ap, void **);

				handler = (erl_rpc_ctx_t *)pkg_malloc(sizeof(erl_rpc_ctx_t));

				if(!handler) {
					LM_ERR("Not enough memory\n");
					goto error;
				}

				if(add_to_recycle_bin(JUNK_PKGCHAR, (void *)handler, ctx)) {
					pkg_free(handler);
					goto error;
				}

				*handler = *ctx;
				handler->no_params = 0;
				handler->reply_params = 0;
				handler->tail = 0;

				/* go where we stopped */
				*(erl_rpc_ctx_t **)void_ptr = handler;

				param->type = ERL_LIST_EXT;
				param->value.handler = (void *)handler;

				break;

			default:
				LM_ERR("Invalid type '%c' in formatting string\n", *fmt);
				goto error;
		}

		erl_rpc_append_param(ctx, param);

		reads++;
		fmt++;
	}
	va_end(ap);
	return reads;

error:

	LM_ERR("Failed to encode parameter #%d into response.\n", reads);
	va_end(ap);
	return -reads;
}

int erl_rpc_array_add(erl_rpc_ctx_t *ctx, char *fmt, ...)
{
	void **void_ptr;
	char *char_ptr;
	str *str_ptr;
	erl_rpc_ctx_t *handler;
	erl_rpc_param_t *param;

	int reads = 0;

	va_list ap;

	va_start(ap, fmt);

	LM_DBG("ctx=%p add fmt=<%s>\n", (void *)ctx, fmt);

	while(*fmt) {
		if((param = erl_new_param(ctx)) == 0) {
			goto error;
		}

		param->member_name = NULL;

		switch(*fmt) {
			case 'b': /* Bool */
			case 't': /* Date and time */
			case 'd': /* Integer */
				param->type = ERL_INTEGER_EXT;
				param->value.n = va_arg(ap, int);
				;
				break;

			case 'f': /* double */
				param->type = ERL_FLOAT_EXT;
				param->value.d = va_arg(ap, double);
				break;

			case 'S': /* str structure */
				str_ptr = va_arg(ap, str *);

				param->type = ERL_STRING_EXT;
				param->value.S = *str_ptr;
				break;

			case 's': /* zero terminated string */

				char_ptr = va_arg(ap, char *);

				param->type = ERL_STRING_EXT;
				param->value.S.len = strlen(char_ptr);

				param->value.S.s = (char *)pkg_malloc(param->value.S.len + 1);

				if(!param->value.S.s) {
					LM_ERR("Not enough memory\n");
					goto error;
				}

				if(add_to_recycle_bin(JUNK_PKGCHAR, param->value.S.s, ctx)) {
					pkg_free(param->value.S.s);
					goto error;
				}

				memcpy(param->value.S.s, char_ptr, param->value.S.len);

				break;

			case '{':
				void_ptr = va_arg(ap, void **);

				handler = (erl_rpc_ctx_t *)pkg_malloc(sizeof(erl_rpc_ctx_t));

				if(!handler) {
					LM_ERR("Not enough memory\n");
					goto error;
				}

				if(add_to_recycle_bin(JUNK_PKGCHAR, (void *)handler, ctx)) {
					pkg_free(handler);
					goto error;
				}

				*handler = *ctx;
				handler->no_params = 0;
				handler->reply_params = 0;
				handler->tail = 0;

				/* go where we stopped */
				*(erl_rpc_ctx_t **)void_ptr = handler;

				param->type = ERL_SMALL_TUPLE_EXT;
				param->value.handler = (void *)handler;

				break;

			case '[':
				void_ptr = va_arg(ap, void **);

				handler = (erl_rpc_ctx_t *)pkg_malloc(sizeof(erl_rpc_ctx_t));

				if(!handler) {
					LM_ERR("Not enough memory\n");
					goto error;
				}

				if(add_to_recycle_bin(JUNK_PKGCHAR, (void *)handler, ctx)) {
					pkg_free(handler);
					goto error;
				}

				*handler = *ctx;
				handler->no_params = 0;
				handler->reply_params = 0;
				handler->tail = 0;

				/* go where we stopped */
				*(erl_rpc_ctx_t **)void_ptr = handler;

				param->type = ERL_LIST_EXT;
				param->value.handler = (void *)handler;

				break;

			default:
				LM_ERR("Invalid type '%c' in formatting string\n", *fmt);
				goto error;
		}

		erl_rpc_append_param(ctx, param);

		reads++;
		fmt++;
	}
	va_end(ap);
	return reads;

error:

	LM_ERR("Failed to encode parameter #%d into response.\n", reads);
	va_end(ap);
	return -reads;
}

int erl_rpc_struct_printf(erl_rpc_ctx_t *ctx, char *name, char *fmt, ...)
{
	int n, buff_size;
	char *buff;
	va_list ap;
	erl_rpc_param_t *param;

	LM_ERR("parsing name:%s fmt: %s\n", name, fmt);

	buff = (char *)pkg_malloc(RPC_BUF_SIZE);
	if(!buff) {
		ERR("No memory left\n");
		return -1;
	}

	buff_size = RPC_BUF_SIZE;

	while(1) {
		/* Try to print in the allocated space. */
		va_start(ap, fmt);
		n = vsnprintf(buff, buff_size, fmt, ap);
		va_end(ap);
		/* If that worked, return the string. */
		if(n > -1 && n < buff_size) {

			if(add_to_recycle_bin(JUNK_PKGCHAR, (void *)buff, ctx)) {
				goto error;
			} else if((param = erl_new_param(ctx))) {
				param->type = ERL_STRING_EXT;
				param->value.S.s = buff;
				param->value.S.len = n;
				param->member_name = name;
				erl_rpc_append_param(ctx, param);
			} else {
				goto error;
			}

			return 0;
		}

		/* Else try again with more space. */
		if(n > -1) {		   /* glibc 2.1 */
			buff_size = n + 1; /* precisely what is needed */
		} else {			   /* glibc 2.0 */
			buff_size *= 2;	   /* twice the old size */
		}
		if((buff = pkg_realloc(buff, buff_size)) == 0) {
			ERR("No memory left\n");
			goto error;
		}
	}

	return 0;

error:
	if(buff)
		pkg_free(buff);
	return -1;
}

int erl_rpc_capabilities(erl_rpc_ctx_t *ctx)
{
	return 0; /* no RPC_DELAYED_REPLY */
}

/** Add a memory to the list of memory blocks that
 * need to be re-claimed later.
 *
 * @param type The type of the memory block.
 * @param ptr A pointer to the memory block.
 * @param ctx The context.
 * @return 0 on success, a negative number on error.
 * @sa empty_recycle_bin()
 */
static int add_to_recycle_bin(int type, void *ptr, erl_rpc_ctx_t *ctx)
{
	struct erl_rpc_garbage *p;

	p = (struct erl_rpc_garbage *)pkg_malloc(sizeof(struct erl_rpc_garbage));

	if(!p) {
		LM_ERR("Not enough memory\n");
		return -1;
	}

	p->type = type;
	p->ptr = ptr;
	p->next = recycle_bin;
	recycle_bin = p;
	return 0;
}

/** Re-claims all memory allocated in the process of building XML-RPC
 * reply.
 */
void empty_recycle_bin(void)
{
	struct erl_rpc_garbage *p;
	/* Collect garbage */
	while(recycle_bin) {
		p = recycle_bin;
		recycle_bin = recycle_bin->next;
		switch(p->type) {
			case JUNK_EI_X_BUFF:

				if(p->ptr) {
					ei_x_free((ei_x_buff *)p->ptr);
					pkg_free(p->ptr);
				}

				break;

			case JUNK_PKGCHAR:

				if(p->ptr) {
					pkg_free(p->ptr);
				}

				break;

			default:
				ERR("BUG: Unsupported junk type\n");
		}

		pkg_free(p);
	}
}

/*
 * Get int parameter
 */
static int get_int(int *int_ptr, erl_rpc_ctx_t *ctx, int reads, int autoconvert)
{
	int type, size;
	char *p;
	char *endptr;
	double d;
	long l;

	if(ei_get_type(ctx->request->buff, &ctx->request_index, &type, &size)) {
		if(ctx->optional)
			return 0;

		erl_rpc_fault(
				ctx, 400, "Can't determine data type of parameter #%d", reads);
		return -1;
	}

	switch(type) {
		case ERL_SMALL_INTEGER_EXT:
		case ERL_INTEGER_EXT:
			if(ei_decode_long(ctx->request->buff, &ctx->request_index, &l)) {
				erl_rpc_fault(ctx, 400, "Bad value of parameter #%d.", reads);
				return -1;
			}
			*int_ptr = (int)l;

			break;
		case ERL_STRING_EXT:
		case ERL_LIST_EXT:

			if(autoconvert == 0) {
				erl_rpc_fault(ctx, 400, "Bad type of parameter #%d", reads);
				return -1;
			}

			/* allocate buffer */
			p = (char *)pkg_malloc(size + 1);

			if(!p) {
				erl_rpc_fault(
						ctx, 500, "Internal Server Error (No memory left)");
				LM_ERR("Not enough memory\n");
				return -1;
			}

			ei_decode_string(ctx->request->buff, &ctx->request_index, p);

			*int_ptr = strtol(p, &endptr, 10);
			if(p == endptr) {
				erl_rpc_fault(ctx, 400,
						"Unable to convert %s into integer for parameter at "
						"position %d",
						p, reads);
				pkg_free(p);
				return -1;
			}

			pkg_free(p);
			break;

		case ERL_FLOAT_EXT:

			if(autoconvert == 0) {
				erl_rpc_fault(ctx, 400, "Bad type of parameter #%d", reads);
				return -1;
			}

			if(ei_decode_double(ctx->request->buff, &ctx->request_index, &d)) {
				erl_rpc_fault(ctx, 400, "Can't read parameter #%d", reads);
				return -1;
			}

			*int_ptr = (int)d;
			break;

		default:
			LM_ERR("Unsupported type ('%c') for conversion into integer "
				   "parameter #%d.\n",
					type, reads);
			erl_rpc_fault(ctx, 400,
					"Unsupported type ('%c') for conversion into integer "
					"parameter #%d.",
					type, reads);
			return -1;
	}

	return 0;
}

static int get_double(
		double *double_prt, erl_rpc_ctx_t *ctx, int reads, int autoconvert)
{
	int type, size;
	char *p;
	char *endptr;
	long n;

	if(ei_get_type(ctx->request->buff, &ctx->request_index, &type, &size)) {
		erl_rpc_fault(
				ctx, 400, "Can't determine data type of parameter #%d", reads);
		return -1;
	}

	switch(type) {
		case ERL_FLOAT_EXT:

			if(ei_decode_double(
					   ctx->request->buff, &ctx->request_index, double_prt)) {
				erl_rpc_fault(ctx, 400, "Bad value of parameter #%d.", reads);
				return -1;
			}

			break;
		case ERL_STRING_EXT:
		case ERL_LIST_EXT:

			if(autoconvert == 0) {
				erl_rpc_fault(ctx, 400, "Bad type of parameter #%d", reads);
				return -1;
			}

			/* allocate buffer */
			p = (char *)pkg_malloc(size + 1);

			if(!p) {
				erl_rpc_fault(
						ctx, 500, "Internal Server Error (No memory left)");
				LM_ERR("Not enough memory\n");
				return -1;
			}

			ei_decode_string(ctx->request->buff, &ctx->request_index, p);

			*double_prt = strtod(p, &endptr);
			if(p == endptr) {
				erl_rpc_fault(ctx, 400,
						"Unable to convert %s into double, parameter at "
						"position #%d",
						p, reads);
				pkg_free(p);
				return -1;
			}

			pkg_free(p);
			break;

		case ERL_SMALL_INTEGER_EXT:
		case ERL_INTEGER_EXT:

			if(autoconvert == 0) {
				erl_rpc_fault(ctx, 400, "Bad type of parameter #%d", reads);
				return -1;
			}

			if(ei_decode_long(ctx->request->buff, &ctx->request_index, &n)) {
				erl_rpc_fault(ctx, 400, "Can't read parameter #%d", reads);
				return -1;
			}

			*double_prt = n;

			break;
		default:
			erl_rpc_fault(
					ctx, 400, "Can't convert to double parameter #%d.", reads);
			return -1;
	}

	return 0;
}

#define MAX_DIGITS 20

static int get_str(str *str_ptr, erl_rpc_ctx_t *ctx, int reads, int autoconvert)
{
	int type, size;
	char *p;
	double d;
	long n;

	if(ei_get_type(ctx->request->buff, &ctx->request_index, &type, &size)) {
		erl_rpc_fault(
				ctx, 400, "Can't determine data type of parameter #%d", reads);
		return -1;
	}

	switch(type) {
		case ERL_FLOAT_EXT:

			if(autoconvert == 0) {
				erl_rpc_fault(ctx, 400, "Bad type of parameter #%d", reads);
				return -1;
			}

			if(ei_decode_double(ctx->request->buff, &ctx->request_index, &d)) {
				erl_rpc_fault(ctx, 400, "Bad value of parameter #%d.", reads);
				return -1;
			}

			p = (char *)pkg_malloc(MAX_DIGITS);

			if(!p) {
				erl_rpc_fault(
						ctx, 500, "Internal Server Error (No memory left)");
				LM_ERR("Not enough memory\n");
				return -1;
			}

			if(add_to_recycle_bin(JUNK_PKGCHAR, p, ctx)) {
				pkg_free(p);
				return -1;
			}

			str_ptr->len = snprintf(p, MAX_DIGITS, "%f", d);
			str_ptr->s = p;

			break;

		case ERL_STRING_EXT:
		case ERL_LIST_EXT:
		case ERL_BINARY_EXT:

			/* allocate buffer */
			p = (char *)pkg_malloc(size + 1);

			if(!p) {
				erl_rpc_fault(
						ctx, 500, "Internal Server Error (No memory left)");
				LM_ERR("Not enough memory\n");
				return -1;
			}

			if(add_to_recycle_bin(JUNK_PKGCHAR, p, ctx)) {
				pkg_free(p);
				return -1;
			}

			if(ei_decode_strorbin(
					   ctx->request->buff, &ctx->request_index, size + 1, p)) {
				erl_rpc_fault(ctx, 400, "Can't read parameter #%d", reads);
				return -1;
			}

			str_ptr->s = p;
			str_ptr->len = size;

			break;

		case ERL_SMALL_INTEGER_EXT:
		case ERL_INTEGER_EXT:

			if(autoconvert == 0) {
				erl_rpc_fault(ctx, 400, "Bad type of parameter #%d", reads);
				return -1;
			}

			if(ei_decode_long(ctx->request->buff, &ctx->request_index, &n)) {
				erl_rpc_fault(ctx, 400, "Bad value of parameter #%d.", reads);
				return -1;
			}

			p = (char *)pkg_malloc(MAX_DIGITS);

			if(!p) {
				erl_rpc_fault(
						ctx, 500, "Internal Server Error (No memory left)");
				LM_ERR("Not enough memory\n");
				return -1;
			}

			if(add_to_recycle_bin(JUNK_PKGCHAR, p, ctx)) {
				pkg_free(p);
				return -1;
			}

			str_ptr->len = snprintf(p, MAX_DIGITS, "%ld", n);
			str_ptr->s = p;

			break;
		default:
			erl_rpc_fault(
					ctx, 400, "Can't convert to string parameter #%d.", reads);
			return -1;
	}

	LM_ERR("parameter #%d:<%.*s>\n", reads, STR_FMT(str_ptr));

	return 0;
}

/*
 * Find member in tuple (aka RPC struct)
 */
static int find_member(erl_rpc_ctx_t *ctx, int arity, const char *member_name)
{
	int index, i = 0;
	int type, size;
	char key_name[MAXATOMLEN];

	/* save position */
	index = ctx->request_index;

	/* { name, Value, name, Value...} */
	while(i < arity) {
		if(ei_get_type(ctx->request->buff, &ctx->request_index, &type, &size)) {
			erl_rpc_fault(ctx, 400, "Bad struct member type");
			goto error;
		}

		if(ei_decode_atom(ctx->request->buff, &ctx->request_index, key_name)) {
			erl_rpc_fault(ctx, 400, "Bad member name");
			goto error;
		}

		if(strcasecmp(member_name, key_name)) {
			if(ei_skip_term(ctx->request->buff, &ctx->request_index)) {
				erl_rpc_fault(ctx, 400, "Unexpected end of struct tuple");
				goto error;
			}
			i++;
		} else {
			/* return at current position */
			return 0;
		}
	}

	erl_rpc_fault(ctx, 400, "Member %s not found", member_name);

error:
	ctx->request_index = index;
	return -1;
}

void init_rpc_handlers()
{
	erl_rpc_func_param.send = (rpc_send_f)erl_rpc_send;
	erl_rpc_func_param.fault = (rpc_fault_f)erl_rpc_fault;
	erl_rpc_func_param.add = (rpc_add_f)erl_rpc_add;
	erl_rpc_func_param.scan = (rpc_scan_f)erl_rpc_scan;
	erl_rpc_func_param.rpl_printf = (rpc_rpl_printf_f)erl_rpc_printf;
	erl_rpc_func_param.struct_add = (rpc_struct_add_f)erl_rpc_struct_add;
	erl_rpc_func_param.array_add = (rpc_array_add_f)erl_rpc_array_add;
	erl_rpc_func_param.struct_scan = (rpc_struct_scan_f)erl_rpc_struct_scan;
	erl_rpc_func_param.struct_printf =
			(rpc_struct_printf_f)erl_rpc_struct_printf;
	erl_rpc_func_param.capabilities = (rpc_capabilities_f)erl_rpc_capabilities;
	erl_rpc_func_param.delayed_ctx_new = 0;
	erl_rpc_func_param.delayed_ctx_close = 0;
}

erl_rpc_param_t *erl_new_param(erl_rpc_ctx_t *ctx)
{
	erl_rpc_param_t *p = (erl_rpc_param_t *)pkg_malloc(sizeof(erl_rpc_param_t));

	if(add_to_recycle_bin(JUNK_PKGCHAR, (void *)p, ctx)) {
		erl_rpc_fault(ctx, 500, "Internal Server Error (No memory left)");
		LM_ERR("Not enough memory\n");

		pkg_free(p);
		return 0;
	}

	p->next = 0;
	p->member_name = 0;
	return p;
}

void erl_rpc_append_param(erl_rpc_ctx_t *ctx, erl_rpc_param_t *param)
{

	if(ctx->tail) {
		ctx->tail->next = param;
		ctx->tail = param;
	} else {
		ctx->reply_params = ctx->tail = param;
	}

	param->next = 0;
	ctx->no_params++;
}
