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

/**
 * send commands using binrpc
 *
 */

#include <sys/types.h>
#include <sys/uio.h>
#include <stdlib.h> /* realloc, rand ... */
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h> /* isprint */
#include <time.h>  /* time */
/* #include <stropts.h>  - is this really needed? --andrei */

#include "../../modules/ctl/ctl_defaults.h" /* default socket & port */
#include "../../modules/ctl/init_socks.h"
#include "../../modules/ctl/binrpc.c" /* ugly hack */

#include "binrpc_api.h"


#define IOVEC_CNT 20
#define TEXT_BUFF_ALLOC_CHUNK 4096
#define FATAL_ERROR -1

#ifndef NAME
#define NAME "binrpc_api"
#endif

#define binrpc_malloc internal_malloc
#define binrpc_realloc internal_realloc
#define binrpc_free internal_free

#ifndef UNIX_PATH_MAX
#define UNIX_PATH_MAX 104
#endif

#ifndef INT2STR_MAX_LEN
#define INT2STR_MAX_LEN (19 + 1 + 1) /* 2^64~= 16*10^18 => 19+1 digits + \0 */
#endif

static void *(*internal_malloc)(size_t size) = malloc;
static void *(*internal_realloc)(void *ptr, size_t size) = realloc;
static void (*internal_free)(void *ptr) = free;

static char binrpc_last_errs[1024] = "";
static int verbose = 0;

char *binrpc_get_last_errs()
{
	return binrpc_last_errs;
}

void binrpc_clear_last_err()
{
	binrpc_last_errs[0] = '\0';
}

void binrpc_set_mallocs(void *_malloc, void *_realloc, void *_free)
{
	internal_malloc = _malloc;
	internal_realloc = _realloc;
	internal_free = _free;
}

static int gen_cookie()
{
	return rand();
}

static void hexdump(unsigned char *buf, int len, int ascii)
{
	int r, i;

	/* dump it in hex */
	for(r = 0; r < len; r++) {
		if((r) && ((r % 16) == 0)) {
			if(ascii) {
				putchar(' ');
				for(i = r - 16; i < r; i++) {
					if(isprint(buf[i]))
						putchar(buf[i]);
					else
						putchar('.');
				}
			}
			putchar('\n');
		}
		printf("%02x ", buf[r]);
	};
	if(ascii) {
		for(i = r; i % 16; i++)
			printf("   ");
		putchar(' ');
		for(i = 16 * (r / 16); i < r; i++) {
			if(isprint(buf[i]))
				putchar(buf[i]);
			else
				putchar('.');
		}
	}
	putchar('\n');
}

/* opens,  and  connects on a STREAM unix socket
 * returns socket fd or -1 on error */
static int connect_unix_sock(char *name, int type, struct sockaddr_un *mysun,
		char *reply_socket, char *sock_dir)
{
	struct sockaddr_un ifsun;
	int s;
	int len;
	int ret;
	int retries;

	retries = 0;
	s = -1;
	memset(&ifsun, 0, sizeof(struct sockaddr_un));
	len = strlen(name);
	if(len > UNIX_PATH_MAX) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"connect_unix_sock: name too long "
				"(%d > %d): %s",
				len, UNIX_PATH_MAX, name);
		goto error;
	}
	ifsun.sun_family = AF_UNIX;
	memcpy(ifsun.sun_path, name, len);
#ifdef HAVE_SOCKADDR_SA_LEN
	ifsun.sun_len = len;
#endif
	s = socket(PF_UNIX, type, 0);
	if(s == -1) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"connect_unix_sock: cannot create unix socket"
				" %s: %s [%d]",
				name, strerror(errno), errno);
		goto error;
	}
	if(type == SOCK_DGRAM) {
		/* we must bind so that we can receive replies */
		if(reply_socket == 0) {
			if(sock_dir == 0)
				sock_dir = "/tmp";
		retry:
			ret = snprintf(mysun->sun_path, UNIX_PATH_MAX, "%s/" NAME "_%d",
					sock_dir, rand());
			if((ret < 0) || (ret >= UNIX_PATH_MAX)) {
				snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
						"connect_unix_sock: buffer overflow while trying to"
						"generate unix datagram socket name");
				goto error;
			}
		} else {
			if(strlen(reply_socket) > UNIX_PATH_MAX) {
				snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
						"connect_unix_sock: buffer overflow while trying to"
						"use the provided unix datagram socket name (%s)",
						reply_socket);
				goto error;
			}
			strcpy(mysun->sun_path, reply_socket);
		}
		mysun->sun_family = AF_UNIX;
		if(bind(s, (struct sockaddr *)&mysun, sizeof(struct sockaddr_un))
				== -1) {
			//if (bind(s, mysun, sizeof(mysun))==-1){
			if(errno == EADDRINUSE && (reply_socket == 0) && (retries < 10)) {
				retries++;
				/* try another one */
				goto retry;
			}
			snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
					"connect_unix_sock: could not bind the unix socket to"
					" %s: %s (%d)",
					mysun->sun_path, strerror(errno), errno);
			goto error;
		}
	}
	if(connect(s, (struct sockaddr *)&ifsun, sizeof(ifsun)) == -1) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"connect_unix_sock: connect(%s): %s [%d]", name,
				strerror(errno), errno);
		goto error;
	}
	return s;
error:
	if(s != -1)
		close(s);
	return FATAL_ERROR;
}

static int connect_tcpudp_socket(char *address, int port, int type)
{
	struct sockaddr_in addr;
	struct hostent *he;
	int sock;

	sock = -1;
	/* resolve destination */
	he = gethostbyname(address);
	if(he == 0) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"connect_tcpudp_socket: could not resolve %s", address);
		goto error;
	}
	/* open socket*/
	memset(&addr, 0, sizeof(struct sockaddr_in));
	addr.sin_family = he->h_addrtype;
	addr.sin_port = htons(port);
	memcpy(&addr.sin_addr.s_addr, he->h_addr_list[0], he->h_length);

	sock = socket(he->h_addrtype, type, 0);
	if(sock == -1) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"connect_tcpudp_socket: socket: %s", strerror(errno));
		goto error;
	}
	if(connect(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr)) != 0) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"connect_tcpudp_socket: connect: %s", strerror(errno));
		goto error;
	}
	return sock;
error:
	if(sock != -1)
		close(sock);
	return FATAL_ERROR;
}

/* on exit cleanup */
static void cleanup(struct sockaddr_un *mysun)
{
	if(mysun->sun_path[0] != '\0') {
		if(unlink(mysun->sun_path) < 0) {
			fprintf(stderr, "ERROR: failed to delete %s: %s\n", mysun->sun_path,
					strerror(errno));
		}
	}
}

int binrpc_open_connection(struct binrpc_handle *handle, char *name, int port,
		int proto, char *reply_socket, char *sock_dir)
{
	struct sockaddr_un mysun;

	binrpc_last_errs[0] = '\0';
	binrpc_last_errs[sizeof(binrpc_last_errs) - 1] =
			'\0'; /* snprintf safe terminator */

	handle->socket = -1;
	handle->buf = NULL;
	mysun.sun_path[0] = '\0';

	if(name == NULL) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"open_connection: invalid IP address or socket name");
		goto error;
	}

	handle->proto = proto;
	switch(proto) {
		case UDP_SOCK:
		case TCP_SOCK:
			if(port == 0) {
				port = DEFAULT_CTL_PORT;
			}

			handle->sock_type = (proto == UDP_SOCK) ? SOCK_DGRAM : SOCK_STREAM;
			if((handle->socket = connect_tcpudp_socket(
						name, port, handle->sock_type))
					< 0) {
				goto error;
			}
			break;
		case UNIXS_SOCK:
		case UNIXD_SOCK:
			handle->sock_type =
					(proto == UNIXD_SOCK) ? SOCK_DGRAM : SOCK_STREAM;
			if((handle->socket = connect_unix_sock(name, handle->sock_type,
						&mysun, reply_socket, sock_dir))
					< 0) {
				goto error;
			}
			break;
		case UNKNOWN_SOCK:
			snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
					"open_connection: Bad socket type for %s\n",
					name); /*vm zmenit*/
			goto error;
	}
	if(handle->sock_type == SOCK_DGRAM) {
		handle->buf_size =
				8192; /* max size of datagram, < SSIZE_MAX, TODO: does a platform dependent constant exist ? */
	} else {
		handle->buf_size = BINRPC_MAX_HDR_SIZE;
	}
	handle->buf = binrpc_malloc(handle->buf_size);
	if(!handle->buf) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"open_connection: not enough memory to allocate buffer. Needed "
				"%d bytes",
				handle->buf_size);
		binrpc_close_connection(handle);
	}
	cleanup(&mysun);
	return 0;

error:
	cleanup(&mysun);
	return FATAL_ERROR;
}

int binrpc_open_connection_url(struct binrpc_handle *handle, char *url)
{
	static char name[100];
	char *c, *c2, *rpl_sock;
	int port, proto, i;
	handle->socket = -1;
	handle->buf = NULL;
	/* parse proto:name:port|unixd_sock */
	c = url;
	if(strncasecmp(c, "udp:", 4) == 0)
		proto = UDP_SOCK;
	else if(strncasecmp(c, "tcp:", 4) == 0)
		proto = TCP_SOCK;
	else if(strncasecmp(c, "unix:", 5) == 0 || strncasecmp(c, "unixs:", 6) == 0)
		proto = UNIXS_SOCK;
	else if(strncasecmp(c, "unixd:", 6) == 0)
		proto = UNIXD_SOCK;
	else {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"open_connection_url: bad protocol in '%s'", c);
		return FATAL_ERROR;
	}
	while(*c != ':')
		c++;
	c++;
	c2 = strchr(c, ':');
	if(!c2)
		c2 = c + strlen(c);
	if(c2 - c > sizeof(name) - 1) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"open_connection_url: name is too long '%s'", c);
		return FATAL_ERROR;
	}
	for(i = 0; c < c2; c++, i++) {
		name[i] = *c;
	}
	name[i] = '\0';
	if(strlen(name) == 0) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"open_connection_url: name is not specified in '%s'", url);
		return FATAL_ERROR;
	}
	c = c2;
	if(*c == ':')
		c++;

	port = 0;
	rpl_sock = NULL;
	switch(proto) {
		case UNIXD_SOCK:
			if(strlen(c) != 0)
				rpl_sock = c;
			break;
		case UNIXS_SOCK:
			break;
		default:
			port = atol(c);
			if(port == 0) {
				snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
						"open_connection_url: port is not specified in '%s'",
						url);
				return FATAL_ERROR;
			}
			break;
	}
	return binrpc_open_connection(handle, name, port, proto, rpl_sock, NULL);
}

void binrpc_close_connection(struct binrpc_handle *handle)
{
	if(handle->socket != -1) {
		close(handle->socket);
		handle->socket = -1;
	}
	if(handle->buf) {
		binrpc_free(handle->buf);
		handle->buf = NULL;
	}
}

/* returns: -1 on error, number of bytes written on success */
static int send_binrpc_cmd(
		struct binrpc_handle *handle, struct binrpc_pkt *pkt, int cookie)
{
	struct iovec v[IOVEC_CNT];
	unsigned char msg_hdr[BINRPC_MAX_HDR_SIZE];
	int n;

	if((n = binrpc_build_hdr(BINRPC_REQ, binrpc_pkt_len(pkt), cookie, msg_hdr,
				BINRPC_MAX_HDR_SIZE))
			< 0) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"send_binrpc_cmd: build header error: %s", binrpc_error(n));
		return FATAL_ERROR;
	}
	v[0].iov_base = msg_hdr;
	v[0].iov_len = n;
	v[1].iov_base = pkt->body;
	v[1].iov_len = binrpc_pkt_len(pkt);
write_again:
	if((n = writev(handle->socket, v, 2)) < 0) {
		if(errno == EINTR)
			goto write_again;
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"send_binrpc_cmd: send packet failed: %s (%d)", strerror(errno),
				errno);
		return FATAL_ERROR;
	}
	return n;
}


/* reads the whole reply
 * returns < 0 on error, reply size on success + initializes resp_handle */
static int get_reply(struct binrpc_handle *handle, int cookie,
		struct binrpc_response_handle *resp_handle)
{
	unsigned char *crt, *hdr_end;
	int n, ret, tl;

	ret = 0;
	resp_handle->reply_buf = NULL;
	hdr_end = crt = handle->buf;

	do {
		n = read(handle->socket, crt, handle->buf_size - (crt - handle->buf));
		if(n <= 0) {
			if(errno == EINTR)
				continue;
			if(n == 0)
				snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
						"get_reply: read unexpected EOF: received %d bytes"
						" of reply",
						(int)(long)(crt - handle->buf));
			else
				snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
						"get_reply: read reply failed: %s (%d)",
						strerror(errno), errno);
			return FATAL_ERROR;
		}
		if(verbose >= 3) {
			/* dump it in hex */
			printf("received %d bytes in reply (@offset %d):\n", n,
					(int)(crt - handle->buf));
			hexdump(crt, n, 1);
		}
		crt += n;
		hdr_end = binrpc_parse_init(
				&resp_handle->in_pkt, handle->buf, crt - handle->buf, &ret);

	} while(ret == E_BINRPC_MORE_DATA);
	if(ret < 0) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"get_reply: reply parsing error: %s", binrpc_error(ret));
		return FATAL_ERROR;
	}

	if(verbose > 1) {
		printf("new packet: type %02x, len %d, cookie %02x\n",
				resp_handle->in_pkt.type, resp_handle->in_pkt.tlen,
				resp_handle->in_pkt.cookie);
	}
	if(resp_handle->in_pkt.cookie != cookie) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"get_reply: reply parsing error: "
				"cookie doesn't match: sent: %02x, received: %02x",
				cookie, resp_handle->in_pkt.cookie);
		return FATAL_ERROR;
	}

	/* we know total size and we can allocate buffer for received data */
	tl = resp_handle->in_pkt.tlen;

	if(handle->sock_type == SOCK_DGRAM) {
		/* we must read all datagram in one read call, otherwise unread part is truncated and lost. Read will block execution */
		if(crt - hdr_end < tl) {
			snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
					"get_reply: datagram truncated. Received: %ld, Expected: "
					"%d.",
					(long int)(crt - hdr_end), tl);
			return FATAL_ERROR;
		}
	}
	if(crt - hdr_end > tl) {
		/* header contains probably data from next message, in case of STREAM it could be unread but it's waste of time */
		crt = hdr_end + tl;
	}

	resp_handle->reply_buf = (unsigned char *)binrpc_malloc(tl);
	if(!resp_handle->reply_buf) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"get_reply: not enough memory to allocate reply buffer. %d "
				"bytes needed.",
				resp_handle->in_pkt.tlen);
		return FATAL_ERROR;
	}
	crt = resp_handle->reply_buf + (crt - hdr_end);
	memcpy(resp_handle->reply_buf, hdr_end, crt - resp_handle->reply_buf);
	tl -= crt - resp_handle->reply_buf;
	while(tl > 0) {
		n = read(handle->socket, crt, tl);
		if(n < 0) {
			if(errno == EINTR)
				continue;
			snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
					"get_reply: read reply failed: %s (%d)", strerror(errno),
					errno);
			binrpc_free(resp_handle->reply_buf);
			resp_handle->reply_buf = NULL;
			return FATAL_ERROR;
		}
		if(verbose >= 3) {
			/* dump it in hex */
			printf("received %d bytes in reply (@offset %d):\n", n,
					(int)(crt - resp_handle->reply_buf));
			hexdump(crt, n, 1);
		}
		crt += n;
		tl -= n;
	}

	return (int)(crt - resp_handle->reply_buf);
}

int binrpc_send_command_ex(struct binrpc_handle *handle, struct binrpc_pkt *pkt,
		struct binrpc_response_handle *resp_handle)
{
	int cookie;

	cookie = gen_cookie();
	if(send_binrpc_cmd(handle, pkt, cookie) < 0) {
		return FATAL_ERROR;
	}
	/* read reply */
	memset(&resp_handle->in_pkt, 0, sizeof(resp_handle->in_pkt));
	if(get_reply(handle, cookie, resp_handle) < 0) {
		return FATAL_ERROR;
	}

	/* normal exit */
	return 0;
}

static int parse_arg(struct binrpc_val *v, char *arg)
{
	int i = 0;
	double f;
	char *tmp;
	int len;

	f = 0.0;

	if(*arg)
		i = strtol(arg, &tmp, 10);
	else
		tmp = 0;
	if((tmp == 0) || (*tmp)) {
		if(*arg)
			f = strtod(arg, &tmp);
		if((tmp == 0) || (*tmp)) {
			/* not an int or a float => string */
			len = strlen(arg);
			if((len >= 2) && (arg[0] == 's') && (arg[1] == ':')) {
				tmp = &arg[2];
				len -= 2;
			} else {
				tmp = arg;
			}
			v->type = BINRPC_T_STR;
			v->u.strval.s = tmp;
			v->u.strval.len = len;
		} else { /* float */
			v->type = BINRPC_T_DOUBLE;
			v->u.fval = f;
		}
	} else { /* int */
		v->type = BINRPC_T_INT;
		v->u.intval = i;
	}
	return 0;
}


/* parse the body into a malloc allocated,  binrpc_val array */
int binrpc_send_command(struct binrpc_handle *handle, char *method, char **args,
		int arg_count, struct binrpc_response_handle *resp_handle)
{
	struct binrpc_pkt req_pkt;
	struct binrpc_val v;
	int i, size, res = FATAL_ERROR, ret = 0;
	unsigned char *req_buf = NULL;

	memset(&resp_handle->in_pkt, 0, sizeof(resp_handle->in_pkt));
	if(!method || strlen(method) == 0) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"send_command: method name not specified");
		goto fail;
	}
	size = BINRPC_MIN_RECORD_SIZE + 8 + strlen(method)
		   + 1; /*max.possible optional value len */
	for(i = 0; i < arg_count; i++) {
		if(parse_arg(&v, args[i]) < 0)
			goto fail;
		switch(v.type) {
			case BINRPC_T_STR:
				size += v.u.strval.len + 1;
				break;
			case BINRPC_T_INT:
			case BINRPC_T_DOUBLE:
				size += sizeof(int);
				break;
			default:
				snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
						"BUG: send_command: unexpected value type");
				goto fail;
		}
		size += BINRPC_MIN_RECORD_SIZE + 8;
	}
	req_buf = binrpc_malloc(size);
	if(!req_buf) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"send_command: not enough memory to allocate buffer. Needed %d "
				"bytes",
				size);
		goto fail;
	}
	if((ret = binrpc_init_pkt(&req_pkt, req_buf, size)) < 0)
		goto fail2;

	if((ret = binrpc_addstr(&req_pkt, method, strlen(method))) < 0)
		goto fail2;

	for(i = 0; i < arg_count; i++) {
		if(parse_arg(&v, args[i]) < 0)
			goto fail;
		switch(v.type) {
			case BINRPC_T_STR:
				if((ret = binrpc_addstr(&req_pkt, v.u.strval.s, v.u.strval.len))
						< 0)
					goto fail2;
				break;
			case BINRPC_T_INT:
				if((ret = binrpc_addint(&req_pkt, v.u.intval)) < 0)
					goto fail2;
				break;
			case BINRPC_T_DOUBLE:
				if((ret = binrpc_adddouble(&req_pkt, v.u.fval)) < 0)
					goto fail2;
				break;
			default:
				break;
		}
	}

	if(binrpc_send_command_ex(handle, &req_pkt, resp_handle) < 0) {
		goto fail;
	}
	res = 0;
fail:
	if(req_buf)
		binrpc_free(req_buf);
	return res;
fail2:
	snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
			"send_command: error when preparing params: %s", binrpc_error(ret));
	goto fail;
}

void binrpc_release_response(struct binrpc_response_handle *resp_handle)
{
	if(resp_handle->reply_buf) {
		binrpc_free(resp_handle->reply_buf);
		resp_handle->reply_buf = NULL;
	}
}

int binrpc_get_response_type(struct binrpc_response_handle *resp_handle)
{
	switch(resp_handle->in_pkt.type) {
		case BINRPC_FAULT:
			return 1;
			break;
		case BINRPC_REPL:
			return 0;
			break;
		default:
			snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
					"BUG: get_response_type: not a reply");
			return FATAL_ERROR;
	}
}

/* parses strings like "bla bla %v 10%% %v\n test=%v",
 * and stops at each %v,  returning  a pointer after the %v, setting *size
 * to the string length (not including %v) and *type to the corresponding
 * BINRPC type (for now only BINRPC_T_ALL).
 * To escape a '%', use "%%", and check for type==-1 (which means skip a call
 *  again parse_fmt).
 * Usage:
 *        n="test: %v,%v,%v\n";
 *        while(*n){
 *          s=n;
 *          n=parse_fmt(n, &type, &size);
 *          printf("%.*s", size, s);
 *          if (type==-1)
 *            continue;
 *          else
 *             printf("now we should get & print an object of type %d\n", type)
 *        }
 */
static char *parse_fmt(char *fmt, int *type, int *size)
{
	char *s;

	s = fmt;
	do {
		for(; *fmt && *fmt != '%'; fmt++)
			;
		if(*fmt == '%') {
			switch(*(fmt + 1)) {
				case 'v':
					*type = BINRPC_T_ALL;
					*size = (int)(fmt - s);
					return (fmt + 2);
					break;
				case '%':
					/* escaped % */
					*size = (int)(fmt - s) + 1;
					*type = -1; /* skip */
					return (fmt + 2);
					break;
			}
		}
	} while(*fmt);
	*type = -1; /* no value */
	*size = (fmt - s);
	return fmt;
}

static void print_binrpc_val(struct binrpc_val *v, int ident)
{
	int r;

	if((v->type == BINRPC_T_STRUCT) && !v->u.end)
		ident--; /* fix to have strut beg. idented differently */
	for(r = 0; r < ident; r++)
		putchar('	');
	if(v->name.s) {
		printf("%.*s: ", v->name.len, v->name.s);
	}
	switch(v->type) {
		case BINRPC_T_INT:
			printf("%d", v->u.intval);
			break;
		case BINRPC_T_STR:
		case BINRPC_T_BYTES:
			printf("%.*s", v->u.strval.len, v->u.strval.s);
			break;
		case BINRPC_T_ARRAY:
			printf("%c", (v->u.end) ? ']' : '[');
			break;
		case BINRPC_T_STRUCT:
			printf("%c", (v->u.end) ? '}' : '{');
			break;
		default:
			printf("ERROR: unknown type %d\n", v->type);
	}
}

int binrpc_print_response(struct binrpc_response_handle *resp_handle, char *fmt)
{
	unsigned char *p;
	unsigned char *end;
	struct binrpc_val val;
	int ret;
	int rec;
	char *f;
	char *s;
	int f_size;
	int fmt_has_values;

	if(!resp_handle) {
		goto error;
	}
	resp_handle->in_pkt.offset = resp_handle->in_pkt.in_struct =
			resp_handle->in_pkt.in_array = 0;

	p = resp_handle->reply_buf;
	end = p + resp_handle->in_pkt.tlen;
	rec = 0;
	f = fmt;
	fmt_has_values = 0;
	/* read body */
	while(p < end) {
		if(f) {

			do {
				if(*f == 0)
					f = fmt; /* reset */
				s = f;
				f = parse_fmt(f, &val.type, &f_size);
				printf("%.*s", f_size, s);
				if(val.type != -1) {
					fmt_has_values = 1;
					goto read_value;
				}
			} while(*f || fmt_has_values);
			val.type = BINRPC_T_ALL;
		} else {
			val.type = BINRPC_T_ALL;
		}
	read_value:
		val.name.s = 0;
		val.name.len = 0;
		p = binrpc_read_record(&resp_handle->in_pkt, p, end, &val, 0, &ret);
		if(ret < 0) {
			if(fmt)
				putchar('\n');
			/*if (ret==E_BINRPC_MORE_DATA)
				goto error_read_again;*/
			if(ret == E_BINRPC_EOP) {
				printf("end of message detected\n");
				break;
			}
			snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
					"error while parsing the record %d,"
					" @%d: %02x : %s",
					rec, resp_handle->in_pkt.offset, *p, binrpc_error(ret));
			goto error;
		}
		rec++;
		if(fmt) {
			print_binrpc_val(&val, 0);
		} else {
			print_binrpc_val(&val, resp_handle->in_pkt.in_struct
										   + resp_handle->in_pkt.in_array);
			putchar('\n');
		}
	}
	if(fmt && *f) {
		/* print the rest, with empty values */
		while(*f) {
			s = f;
			f = parse_fmt(f, &val.type, &f_size);
			printf("%.*s", f_size, s);
		}
	}
	return 0;
error:
	return FATAL_ERROR;
	/*error_read_again:
	snprintf(binrpc_last_errs, sizeof(binrpc_last_errs)-1,
		"ERROR: more data needed");
	return -2;
	*/
}


void binrpc_free_rpc_array(struct binrpc_val *a, int size)
{
	int r;
	for(r = 0; r < size; r++) {
		if(a[r].name.s)
			binrpc_free(a[r].name.s);
		if((a[r].type == BINRPC_T_STR || a[r].type == BINRPC_T_BYTES)
				&& a[r].u.strval.s) {
			binrpc_free(a[r].u.strval.s);
		}
	}
	binrpc_free(a);
}


#define VAL_ARRAY_CHUNK 100
int binrpc_parse_response(struct binrpc_val **vals, int *val_count,
		struct binrpc_response_handle *resp_handle)
{
	struct binrpc_val val;
	unsigned char *p, *end;
	int ret, i;

	resp_handle->in_pkt.offset = resp_handle->in_pkt.in_struct =
			resp_handle->in_pkt.in_array = 0;

	i = 0;
	if(*val_count == 0) {
		*val_count = VAL_ARRAY_CHUNK; /* start with a reasonable size */
	}
	*vals = (struct binrpc_val *)binrpc_malloc(*val_count * sizeof(**vals));
	if(*vals == 0)
		goto error_mem;
	p = resp_handle->reply_buf;
	end = p + resp_handle->in_pkt.tlen;

	/* read body */
	while(p < end) {
		val.type = BINRPC_T_ALL;
		val.name.s = 0;
		val.name.len = 0;
		p = binrpc_read_record(&resp_handle->in_pkt, p, end, &val, 0, &ret);
		if(ret < 0) {
			if(ret == E_BINRPC_EOP) {
				break;
			}
			snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
					"ERROR while parsing the record %d,"
					" @%d: %02x : %s",
					i, resp_handle->in_pkt.offset, *p, binrpc_error(ret));
			goto error;
		}
		if(i >= *val_count) {
			struct binrpc_val *t;
			t = (struct binrpc_val *)binrpc_realloc(
					*vals, (VAL_ARRAY_CHUNK + (*val_count)) * sizeof(**vals));
			if(t == 0)
				goto error_mem;
			*vals = t;
			*val_count += VAL_ARRAY_CHUNK;
		}
		(*vals)[i] = val;
		if(val.name.s) {
			if(((*vals)[i].name.s = binrpc_malloc(val.name.len + 1)) == 0)
				goto error_mem;
			memcpy((*vals)[i].name.s, val.name.s, val.name.len);
			(*vals)[i].name.s[val.name.len] = 0; /* 0-term */
		}
		if(val.u.strval.s) {
			if(val.type == BINRPC_T_STR) {
				if(((*vals)[i].u.strval.s = binrpc_malloc(val.u.strval.len + 1))
						== 0)
					goto error_mem;
				memcpy((*vals)[i].u.strval.s, val.u.strval.s, val.u.strval.len);
				(*vals)[i].u.strval.s[val.u.strval.len] = 0; /* 0-term */
			} else if(val.type == BINRPC_T_BYTES) {
				if(((*vals)[i].u.strval.s = binrpc_malloc(val.u.strval.len))
						== 0)
					goto error_mem;
				memcpy((*vals)[i].u.strval.s, val.u.strval.s, val.u.strval.len);
			}
		}
		i++;
	}
	if(i == 0) {
		binrpc_free(*vals);
		*vals = NULL;
	} else if(i < *val_count) {
		/*		do not try to save memory because it causes fragmentation when used ser mem utils and "regular" memory leak
		struct binrpc_val *t;
		t = (struct binrpc_val*) binrpc_realloc(*vals, i*sizeof(**vals));
		if (t) *vals = t;
*/
	}
	*val_count = i;
	return 0;
error_mem:
	snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
			"parse_response: out of memory");
error:
	if(*vals) {
		binrpc_free_rpc_array(*vals, i);
		*vals = NULL;
	}
	*val_count = 0;
	return FATAL_ERROR;
}

int binrpc_parse_error_response(
		struct binrpc_response_handle *resp_handle, int *err_no, char **err)
{
	struct binrpc_val val;
	unsigned char *p, *end;
	int ret;

	resp_handle->in_pkt.offset = resp_handle->in_pkt.in_struct =
			resp_handle->in_pkt.in_array = 0;
	p = resp_handle->reply_buf;
	end = p + resp_handle->in_pkt.tlen;

	val.type = BINRPC_T_INT;
	val.name.s = 0;
	val.name.len = 0;
	p = binrpc_read_record(&resp_handle->in_pkt, p, end, &val, 0, &ret);
	if(ret < 0) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"parse_error_response: error when parsing reply (code): %s",
				binrpc_error(ret));
		return FATAL_ERROR;
	}
	*err_no = val.u.intval;

	val.type = BINRPC_T_STR;
	p = binrpc_read_record(&resp_handle->in_pkt, p, end, &val, 0, &ret);
	if(ret < 0) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"parse_error_response: error when parsing reply (str): %s",
				binrpc_error(ret));
		return FATAL_ERROR;
	}
	*err = val.u.strval.s; /* it's null terminated */
	return 0;
}

/* returns a pointer to a static buffer containing l in asciiz & sets len */
static inline char *int2str_internal(unsigned int l, int *len)
{
	static char r[INT2STR_MAX_LEN];
	int i;

	i = INT2STR_MAX_LEN - 2;
	r[INT2STR_MAX_LEN - 1] = 0; /* null terminate */
	do {
		r[i] = l % 10 + '0';
		i--;
		l /= 10;
	} while(l && (i >= 0));
	if(l && (i < 0)) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"BUG: int2str_internal: overflow");
	}
	if(len)
		*len = (INT2STR_MAX_LEN - 2) - i;
	return &r[i + 1];
}

static int realloc_buf(unsigned char **buf, int *buf_len, int req_len)
{
	unsigned char *tmp_buf;
	int orig_len;

	orig_len = (*buf == NULL) ? 0 : strlen((char *)*buf);
	*buf_len += (TEXT_BUFF_ALLOC_CHUNK < req_len)
						? TEXT_BUFF_ALLOC_CHUNK + req_len
						: TEXT_BUFF_ALLOC_CHUNK;

	if(*buf == NULL)
		tmp_buf = (unsigned char *)binrpc_malloc(orig_len + *buf_len);
	else
		tmp_buf = (unsigned char *)binrpc_realloc(*buf, orig_len + *buf_len);
	if(tmp_buf == 0) {
		snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
				"ERROR: out of memory");
		return FATAL_ERROR;
	}

	*buf = tmp_buf;
	(*buf)[orig_len] = '\0';

	return 0;
}

static inline int str2buffer(
		unsigned char **buf, int *buf_len, int *pos, char *data, int data_len)
{
	if(*buf_len < data_len) {
		if(realloc_buf(buf, buf_len, data_len) != 0) {
			return FATAL_ERROR;
		}
	}

	memcpy(&(*buf)[*pos], data, data_len);
	*pos += data_len;
	*buf_len -= data_len;

	return 0;
}

static inline int char2buffer(
		unsigned char **buf, int *buf_len, int *pos, char data)
{
	if(*buf_len < 1) {
		if(realloc_buf(buf, buf_len, 1) != 0) {
			return FATAL_ERROR;
		}
	}

	(*buf)[*pos] = data;
	++(*pos);
	--(*buf_len);

	return 0;
}

static int val2buffer(
		struct binrpc_val *v, unsigned char **buf, int *buf_len, int *pos)
{
	char *number;
	int num_len;

	if(v->name.s) {
		if(str2buffer(buf, buf_len, pos, v->name.s, v->name.len) != 0) {
			return FATAL_ERROR;
		}
		if(str2buffer(buf, buf_len, pos, ": ", strlen(": "))
				!= 0) { /* TODO: common format */
			return FATAL_ERROR;
		}
	}

	switch(v->type) {
		case BINRPC_T_INT:
			num_len = 0;
			number = NULL;
			number = int2str_internal(v->u.intval, &num_len);
			if(number == NULL) {
				printf("ERROR: Conversion of %d into string failed.\n",
						v->type);
				return FATAL_ERROR;
			}

			if(str2buffer(buf, buf_len, pos, number, num_len) != 0) {
				return FATAL_ERROR;
			}
			break;
		case BINRPC_T_STR:
		case BINRPC_T_BYTES:
			if(str2buffer(buf, buf_len, pos, v->u.strval.s, v->u.strval.len)
					!= 0) {
				return FATAL_ERROR;
			}
			break;
		case BINRPC_T_ARRAY:
			if(char2buffer(buf, buf_len, pos, (v->u.end) ? ']' : '[') != 0) {
				return FATAL_ERROR;
			}
			break;
		case BINRPC_T_STRUCT:
			if(char2buffer(buf, buf_len, pos, (v->u.end) ? '}' : '{') != 0) {
				return FATAL_ERROR;
			}
			break;
		default:
			printf("ERROR: unknown type %d\n", v->type);
			return FATAL_ERROR;
	};

	return 0;
}

int binrpc_response_to_text(struct binrpc_response_handle *resp_handle,
		unsigned char **txt_rsp, int *txt_rsp_len, char delimiter)
{
	unsigned char *p;
	unsigned char *end;
	struct binrpc_val val;
	int ret;
	int rec;
	int pos;

	pos = 0;

	if(!resp_handle) {
		goto error;
	}

	memset(&val, 0, sizeof(struct binrpc_val));
	resp_handle->in_pkt.offset = resp_handle->in_pkt.in_struct =
			resp_handle->in_pkt.in_array = 0;

	p = resp_handle->reply_buf;
	end = p + resp_handle->in_pkt.tlen;
	rec = 0;

	if(*txt_rsp == NULL) {
		*txt_rsp_len = 0;
		if(realloc_buf(txt_rsp, txt_rsp_len, 0) != 0) {
			goto error;
		}
	}

	/* read body */
	while(p < end) {
		val.type = BINRPC_T_ALL;
		val.name.s = 0;
		val.name.len = 0;
		p = binrpc_read_record(&resp_handle->in_pkt, p, end, &val, 0, &ret);
		if(ret < 0) {
			if(ret == E_BINRPC_EOP) {
				printf("end of message detected\n");
				break;
			}
			snprintf(binrpc_last_errs, sizeof(binrpc_last_errs) - 1,
					"ERROR while parsing the record %d,"
					" @%d: %02x : %s",
					rec, resp_handle->in_pkt.offset, *p, binrpc_error(ret));
			goto error;
		}
		rec++;
		if(val2buffer(&val, txt_rsp, txt_rsp_len, &pos) != 0) {
			goto error;
		}

		if(char2buffer(txt_rsp, txt_rsp_len, &pos, delimiter) != 0) {
			goto error;
		}
	}

	/* rewrite last char - we don't need delimiter there */
	(*txt_rsp)[pos - 1] = '\0';
	/*
	if(char2buffer(txt_rsp, txt_rsp_len, &pos, '\0') != 0) {
		goto error;
	}
	*/
	return 0;
error:
	return FATAL_ERROR;
}

int main(int argc, char **argv)
{
	struct binrpc_response_handle resp_handle;
	unsigned char *txt_rsp = NULL;
	int txt_rsp_len = 0;
	struct binrpc_handle handle;
	struct binrpc_val *vals = NULL;
	int cnt, i, err_no;
	char *errs;

	if(argc < 2)
		goto err;

	if(binrpc_open_connection_url(&handle, argv[1]) < 0)
		goto err2;
	if(binrpc_send_command(&handle, argv[2], argv + 3, argc - 3, &resp_handle)
			< 0) {
		binrpc_close_connection(&handle);
		goto err2;
	}
	binrpc_close_connection(&handle);

	if(binrpc_response_to_text(&resp_handle, &txt_rsp, &txt_rsp_len, '\n') < 0)
		goto err3;
	fprintf(stdout,
			"binrpc_response_to_text():\n--------------------------\n%s\n",
			txt_rsp);

	fprintf(stdout, "\nbinrpc_print_response():\n------------------------\n");
	binrpc_print_response(&resp_handle, NULL);

	fprintf(stdout, "\nbinrpc_parse_response():\n------------------------\n");
	cnt = 0;
	switch(binrpc_get_response_type(&resp_handle)) {
		case 0:
			if(binrpc_parse_response(&vals, &cnt, &resp_handle) < 0)
				goto err3;
			fprintf(stdout, "#Records: %d\n", cnt);
			for(i = 0; i < cnt; i++) {
				fprintf(stdout, "#%.2d: type:%d name:%.*s\n", i, vals[i].type,
						vals[i].name.len, vals[i].name.s);
			}
			break;
		case 1:
			if(binrpc_parse_error_response(&resp_handle, &err_no, &errs) < 0)
				goto err3;
			fprintf(stdout, "%d %s\n", err_no, errs);
			break;
		default:
			fprintf(stdout, "Unknown response type: %d\n",
					binrpc_get_response_type(&resp_handle));
			break;
	}

	if(vals != NULL) {
		binrpc_free_rpc_array(vals, cnt);
	}
	if(txt_rsp != NULL) {
		binrpc_free(txt_rsp);
	}
	binrpc_release_response(&resp_handle);

	return 0;
err:
	fprintf(stderr, "Usage: %s url mathod [params]\n", NAME);
	return -1;
err3:
	if(vals != NULL) {
		binrpc_free_rpc_array(vals, cnt);
	}
	if(txt_rsp) {
		binrpc_free(txt_rsp);
	}
	binrpc_release_response(&resp_handle);
err2:
	fprintf(stderr, "ERROR: %s\n", binrpc_get_last_errs());
	return -2;
}
