/*
 * Created by jiaqiang.ye@lnxall.com
 *
 * NOTE: based upon the original work of Chen Xiaohui
 *
 * 2023/02/07
 */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <sys/un.h>

#include <libubus.h>
#include <libubox/blobmsg_json.h>
#include <pthread.h>
#include <stdarg.h>
#include <sys/prctl.h>

#include "lnxall_ubuslog.h"
#include <syslog.h>

#ifndef SYSLOG_PADDING
#define SYSLOG_PADDING        16                        /* defined in ubox/log/syslog.c */
#endif
#define LNXAI_APP_LEN         64                        /* maximum length of application name */
#define LNXALL_LOGFILE        "/dev/console"

/* lnxall Application Information */
struct lnxall_appinfo {
	pthread_t                 thid;                     /* pthread ID for working thread */
	pid_t                     appPid;                   /* PID of running process */
	char                      appName[LNXAI_APP_LEN];   /* name of application */
	struct ubus_context *     uctx;                     /* ubus connection handle */
	struct ubus_event_handler evthandler;               /* ubus event handler */
	int                       logOpts;                  /* log options */
	int                       logfd;                    /* file descriptor for /dev/console */
	int                       logLevel;                 /* application specific loglevel */
};

static struct lnxall_appinfo g_appinfo;

/*
 * Automatically called entry function.
 * On shared library load, the function
 * will be called by dynamic linker.
 */
static void lnxall_ubuslog_init(void)
	__attribute__((constructor, used));

static int ubus_loglevel_set(struct ubus_context * ctx,
	struct ubus_object * obj, struct ubus_request_data * req,
	const char * method, struct blob_attr * msg);
static int ubus_loglevel_get(struct ubus_context * ctx,
	struct ubus_object * obj, struct ubus_request_data * req,
	const char * method, struct blob_attr * msg);

enum {
	POLICY_LOGLEVEL_SET = 0,
	POLICY_LOGLEVEL_GET,
	LNXALL_LEVEL_POLICY_MAX,
};

static const struct blobmsg_policy lnxall_policies[] = {
	[POLICY_LOGLEVEL_SET] = { .name = "loglevel",   .type = BLOBMSG_TYPE_INT32 },
	[POLICY_LOGLEVEL_GET] = { .name = "appname",    .type = BLOBMSG_TYPE_STRING },
};

static const struct ubus_method lnxall_ubus_methods[] = {
	UBUS_METHOD("loglevel_set", ubus_loglevel_set, lnxall_policies),
	UBUS_METHOD("loglevel_get", ubus_loglevel_get, lnxall_policies),
};

int lnxall_loglevel_set(int newlevel, int dftsig)
{
	struct lnxall_appinfo * appi = &g_appinfo;

	if (newlevel >= LNXALL_LOGEMERG && newlevel <= LNXALL_LOGDEBUG) {
		__atomic_store_n(&(appi->logLevel), newlevel, __ATOMIC_SEQ_CST);
		if (dftsig != 0) {
			int error;
			struct timespec tspec;
			tspec.tv_sec = 1;
			tspec.tv_nsec = 0;
			if (dftsig >= 150 && dftsig <= 2000) {
				tspec.tv_sec = (time_t) (dftsig / 1000);
				dftsig %= 1000;
				tspec.tv_nsec = (long) (dftsig * 1000000);
			}
			error = nanosleep(&tspec, NULL) == -1;
			error |= (signal(SIGINT, SIG_DFL) == SIG_ERR) ? 0x2 : 0;
			error |= (signal(SIGTERM, SIG_DFL) == SIG_ERR) ? 0x4 : 0;
			error |= (signal(SIGCHLD, SIG_DFL) == SIG_ERR) ? 0x8 : 0;
			if (error) {
				fprintf(stderr, "Error, failed to restore default actions for SIGING/SIGTERM: %d\n", error);
				fflush(stderr);
			}
		}
		return 0;
	}
	return -1;
}

int lnxall_loglevel_get(void)
{
	int rval = 0;
	struct lnxall_appinfo * appi = &g_appinfo;
	__atomic_load(&(appi->logLevel), &rval, __ATOMIC_ACQUIRE);
	return rval;
}

int ubus_loglevel_set(struct ubus_context * ctx,
	struct ubus_object * obj, struct ubus_request_data * req,
	const char * method, struct blob_attr * msg)
{
	int level = 0;
	int ret = 1, idx;
	struct blob_buf bufp;
	struct blob_attr * tb[LNXALL_LEVEL_POLICY_MAX];

	memset(&bufp, 0, sizeof(bufp));
	for (idx = 0; idx < LNXALL_LEVEL_POLICY_MAX; ++idx)
		tb[idx] = NULL;

	blobmsg_parse(lnxall_policies, LNXALL_LEVEL_POLICY_MAX,
		tb, blob_data(msg), blob_len(msg));
	if (tb[POLICY_LOGLEVEL_SET] != NULL) {
		level = blobmsg_get_u32(tb[POLICY_LOGLEVEL_SET]);
		ret = lnxall_loglevel_set(level, 0);
		if (ret < 0)
			ret = 2;
	}

	blob_buf_init(&bufp, 0);
	blobmsg_add_u32(&bufp, "result", (uint32_t) ret);
	ubus_send_reply(ctx, req, bufp.head);
	blob_buf_free(&bufp);
	return 0;
}

int ubus_loglevel_get(struct ubus_context * ctx,
	struct ubus_object * obj, struct ubus_request_data * req,
	const char * method, struct blob_attr * msg)
{
	int idx;
	struct blob_buf bufp;
#if 0
	struct blob_attr * tb[LNXALL_LEVEL_POLICY_MAX];

	for (idx = 0; idx < LNXALL_LEVEL_POLICY_MAX; ++idx)
		tb[idx] = NULL;

	blobmsg_parse(lnxall_policies, LNXALL_LEVEL_POLICY_MAX,
		tb, blob_data(msg), blob_len(msg));
	if (tb[POLICY_LOGLEVEL_GET] != NULL) {
		const char * appn = blobmsg_get_string(tb[POLICY_LOGLEVEL_GET]);
		if (appn == NULL)
			appn = "unknown";
		fprintf(stdout, "Getting loglevel for application: %s\n", appn);
		fflush(stdout);
	}
#endif

	memset(&bufp, 0, sizeof(bufp));
	blob_buf_init(&bufp, 0);
	idx = lnxall_loglevel_get();
	blobmsg_add_u32(&bufp, "loglevel", (uint32_t) idx);
	ubus_send_reply(ctx, req, bufp.head);
	blob_buf_free(&bufp);
	return 0;
}

static void ubus_event_loglevel(struct ubus_context * ctx,
	struct ubus_event_handler * ev, const char * type, struct blob_attr * msg)
{
	const char * appn;
	int idx, level = 0;
	struct lnxall_appinfo * appi;
	struct blob_attr * tb[LNXALL_LEVEL_POLICY_MAX];

	appn = NULL;
	appi = &g_appinfo;
	for (idx = 0; idx < LNXALL_LEVEL_POLICY_MAX; ++idx)
		tb[idx] = NULL;

	blobmsg_parse(lnxall_policies, LNXALL_LEVEL_POLICY_MAX,
		tb, blob_data(msg), blob_len(msg));
	appn = (tb[POLICY_LOGLEVEL_GET] != NULL) ?
		blobmsg_get_string(tb[POLICY_LOGLEVEL_GET]) : NULL;
	if (appn && strcasecmp(appn, appi->appName) != 0) {
#if 1
		fprintf(stderr, "INFO: loglevel event directed to '%s' only\n", appn);
		fflush(stderr);
#endif
		return;
	}

	if (tb[POLICY_LOGLEVEL_SET] != NULL) {
		level = blobmsg_get_u32(tb[POLICY_LOGLEVEL_SET]);
		if (lnxall_loglevel_set(level, 0) == 0) {
#if 0
		fprintf(stdout, "Handling loglevel event, loglevel: %d\n", level);
		fflush(stdout);
#endif
		}
	}
}

static void lnxall_fdcloexec(int start, int end)
{
	int fd, ret;

	for (fd = start; fd <= end; ++fd) {
		ret = fcntl(fd, F_GETFD, 0);
		if (ret == -1)
			continue;
		if (ret & FD_CLOEXEC)
			continue;
		ret = fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
		if (ret == -1) {
			fprintf(stderr, "Error, failed to set CLOEXEC for %d: %s\n",
				fd, strerror(errno));
			fflush(stderr);
		}
	}

}

static struct ubus_object_type lnxall_object_type =
	UBUS_OBJECT_TYPE("lnxall_methods", lnxall_ubus_methods);

static struct ubus_object lnxall_objects = {
	.name = "test_name",
	.type = &lnxall_object_type,
	.methods = lnxall_ubus_methods,
	.n_methods = ARRAY_SIZE(lnxall_ubus_methods),
};

static void * lnxall_pthread(void * arg)
{
	int ret, error;
	char logthread[16];
	struct ubus_object * pobj;
	struct ubus_context * uctx;
	struct lnxall_appinfo * appi;

	error = 0;
	pobj = &lnxall_objects;
	appi = (struct lnxall_appinfo *) arg;

	// disable pthread_cancel to avoid canceled `malloc lock state:
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &error);

	error = 0;
	if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) {
		error = errno;
		fprintf(stderr, "Error, failed to ignore SIGPIPE: %s\n", strerror(error));
		fflush(stderr);
		exit(26);
	}

	strcpy(logthread, "ubuslog_thread");
	ret = prctl(PR_SET_NAME, (unsigned long) logthread, 0, 0);
	if (ret == -1) {
		error = errno;
		fprintf(stderr, "Error, failed to set thread name: %s\n", strerror(error));
		fflush(stderr);
		exit(27);
	}

	/* free existing ubus connect handle */
	uctx = appi->uctx;
	if (uctx != NULL) {
		ubus_free(uctx);
		appi->uctx = NULL;
	}

	/* re-initialize uloop library */
	uloop_init();

	/* create a new ubus connection handle */
	uctx = ubus_connect(NULL);
	if (uctx == NULL) {
		error = 0;
		pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &error);
		fputs("Failed to connect to ubus\n", stderr);
		fflush(stderr);
		return NULL;
	}
	appi->uctx = uctx;

	/* add ubus connection handle to uloop */
	ubus_add_uloop(uctx);

	/* use application name instead of `test_name */
	pobj->name = appi->appName;

	/* register ubus callable object */
	ret = ubus_add_object(uctx, pobj);
	if (ret) {
		fprintf(stderr, "Failed to add object '%s': %s\n",
			pobj->name, ubus_strerror(ret));
		fflush(stderr);
	}

	/* initialize and register ubus event handler structure */
	memset(&appi->evthandler, 0, sizeof(struct ubus_event_handler));
	appi->evthandler.cb = ubus_event_loglevel;
	ret = ubus_register_event_handler(uctx, &appi->evthandler, "loglevel_setall");
	if (ret != 0) {
		fprintf(stderr, "Error, ubus_register_event_handler has returned: %d\n", ret);
		fflush(stderr);
	}

	error = 0;
	/* working thread can be terminated with `pthread_cancel */
	ret = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &error);
	if (ret) {
		fprintf(stderr, "Error, failed to set thread cancel state: %d (%s)\n",
			ret, strerror(ret));
		fflush(stderr);
	}

	ret = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &error);
	if (ret) {
		fprintf(stderr, "Error, failed to set thread cancel type: %d (%s)\n",
			ret, strerror(ret));
		fflush(stderr);
	}

	/* forbid child process to inherit file descriptors: */
	lnxall_fdcloexec(STDERR_FILENO + 1, 1024);

	/* Note: application hangs in `uloop_run */
	for (;;) {
		ret = uloop_run();
		if (ret == 0 && uloop_cancelled)
			break;
	}

	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &error);
	/* make some noise */
	fputs("Warning! lnxall ubuslog will now terminate!\n", stderr);
	fflush(stderr);
	ubus_remove_object(uctx, pobj);
	ubus_unregister_event_handler(uctx, &appi->evthandler);
	pobj->name = "dummy_name";

	return NULL;
}

static int lnxall_open_logfd(const char * fpath, int verbose)
{
	int fd, error;

	error = 0;
	if (fpath == NULL)
		fpath = LNXALL_LOGFILE;
	fd = open(fpath, O_WRONLY | O_CLOEXEC | O_NOCTTY, 0644);
	if (fd == -1 && verbose) {
		error = errno;
		fprintf(stderr, "Error, failed to open(%s): %s\n",
			fpath, strerror(error));
		fflush(stderr);
		errno = error;
	}
	return fd;
}

static int lnxall_connect_logserver(const char * upath, int verbose)
{
	int fd, error;
	int idx, newfd;
	struct sockaddr_un addr;

	error = 0;
	newfd = -1;
	if (upath == NULL)
		upath = "/dev/log";
	fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
	if (fd == -1) {
		if (verbose) {
			error = errno;
			fprintf(stderr, "Error, failed to create UNIX domain socket: %s\n",
				strerror(error));
			fflush(stderr);
			errno = error;
		}
		return -1;
	}

	/* initialize unix domain socket */
	memset(&addr, 0, sizeof(addr));
	addr.sun_family = AF_UNIX;
	strncpy(addr.sun_path, upath, sizeof(addr.sun_path) - 1);

	/* IMPORTANT: connect to local log server, not remote log server */
	error = connect(fd, (const struct sockaddr *) &addr, sizeof(addr));
	if (error == -1) {
		error = errno;
		if (verbose) {
			fprintf(stderr, "Error, failed to connect to logserver: %s\n",
				strerror(error));
			fflush(stderr);
		}
		close(fd);
		errno = error;
		return -2;
	}

	/* determine an unused high order */
	for (idx = 512; idx >= 3; idx--) {
		struct stat fst;
		if (fstat(idx, &fst) == -1) {
			error = errno;
			if (error == EBADF) {
				newfd = idx;
				break;
			}
		}
	}

	if (newfd > 0 && newfd != fd && dup3(fd, newfd, O_CLOEXEC) >= 0) {
		close(fd);
		fd = newfd;
	}
	return fd;
}

static int basename_exec(char * out, size_t outlen)
{
	int error;
	ssize_t rval;
	char * tmpbuf, * strp;

	error = 0;
	tmpbuf = strp = NULL;

	/* allocate temporary buffer */
#define LUI_TMPBUF_SIZE 4096
	tmpbuf = (char *) calloc(0x1, LUI_TMPBUF_SIZE);
	if (tmpbuf == NULL) {
		error = errno;
		fputs("Error, system out of memory!\n", stderr);
		fflush(stderr);
		errno = error;
		return -1;
	}

	/* determine the path of executable binary */
	rval = readlink("/proc/self/exe", tmpbuf, LUI_TMPBUF_SIZE - 1);
	if (rval <= 0) {
		error = errno;
		fprintf(stderr, "Error, readlink(\"/proc/self/exe\") has failed: %s\n",
			strerror(error));
		fflush(stderr);
		free(tmpbuf);
		errno = error;
		return -2;
	}

	/* extract the basename of executable binary */
	strp = strrchr(tmpbuf, '/');
	if (strp == NULL)
		strp = tmpbuf;
	else
		strp++;

	strncpy(out, strp, outlen);
	free(tmpbuf);
	return 0;
}

int lnxall_log_reinit(const char * appn, int dftsig)
{
	int ret;
	pid_t newpid;
	int logfd, oldfd;
	pthread_t newt, oldt;
	struct lnxall_appinfo * appi;
	char appname[LNXAI_APP_LEN / 2];

	appi = &g_appinfo;
	oldfd = logfd = -1;
	newt = oldt = (pthread_t) 0ul;

	newpid = getpid();
	__atomic_exchange(&(appi->thid), &newt, &oldt, __ATOMIC_SEQ_CST);
	if (oldt && appi->appPid == newpid) {
		/* terminate working thread which hangs at `uloop_run: */
		void * rval = NULL;
		int r0 = pthread_cancel(oldt);
		if (r0 == 0)
			pthread_join(oldt, &rval);
	}

	/* update application PID, which might change after `fork() system call: */
	appi->appPid = newpid;

	/* DO CHANGE `appName with identifying PID */
	if (appn == NULL) {
		memset(appname, 0, sizeof(appname));
		basename_exec(appname, sizeof(appname) - 1);
		snprintf(appi->appName, LNXAI_APP_LEN, "%s-%ld",
			appname, (long) newpid);
	} else {
		strncpy(appi->appName, appn, LNXAI_APP_LEN);
	}
	appi->appName[LNXAI_APP_LEN - 1] = '\0';

	/* inherit ubus_connect handle from parent process */
	/* appi->uctx = ... ; */

	logfd = lnxall_connect_logserver(NULL, 0);
	__atomic_exchange(&(appi->logfd), &logfd, &oldfd, __ATOMIC_SEQ_CST);
	if (oldfd >= 0 && oldfd != logfd)
		close(oldfd);

	newt = 0;
	/* create new working thread */
	ret = pthread_create(&newt, NULL, lnxall_pthread, appi);
	if (ret) {
		fprintf(stderr, "Error, failed to create working thread: %d (%s)\n",
			ret, strerror(ret));
		fflush(stderr);
		return -1;
	}
	__atomic_store(&(appi->thid), &newt, __ATOMIC_SEQ_CST);

	if (dftsig != 0) {
		struct timespec tspec;
		tspec.tv_sec = 1;
		tspec.tv_nsec = 0;
		nanosleep(&tspec, NULL);
		signal(SIGINT, SIG_DFL);
		signal(SIGCHLD, SIG_DFL);
		signal(SIGTERM, SIG_DFL);
	}
	return 0;
}

void lnxall_ubuslog_init(void)
{
	int ret;
	struct timespec tspec;
	struct lnxall_appinfo * appi;

	appi = &g_appinfo;

	/* set global variable `g_appinfo to zero */
	memset(appi, 0, sizeof(*appi));

	/* initialize global variable `g_appinfo */
	appi->appPid = getpid();
	basename_exec(appi->appName, LNXAI_APP_LEN - 1);
	appi->logfd = lnxall_connect_logserver(NULL, 1);
	appi->logLevel = LNXALL_LOGERR;

	/* create lnxall ubus working thread */
	ret = pthread_create(&(appi->thid), NULL, lnxall_pthread, (void *) appi);
	if (ret != 0) {
		fprintf(stderr, "Error, failed to create lnxall working thread: %d\n", ret);
		fflush(stderr);
		exit(27);
	}

	/* delay 0.21 second to avoid musl/malloc race conditions */
	tspec.tv_sec = 0;
	tspec.tv_nsec = 210 * 1000000;
	nanosleep(&tspec, NULL);
	signal(SIGCHLD, SIG_DFL);
}

#define ADD_TIMESTR 0
#if ADD_TIMESTR
static const char * get_timestr(char * timp, size_t tlen)
{
	int ret;
	size_t rval;
	struct tm tim;
	struct timespec tspec;

	memset(timp, 0, tlen);
	tspec.tv_sec = 0;
	tspec.tv_nsec = 0;
	ret = clock_gettime(CLOCK_REALTIME, &tspec);
	if (ret == -1) {
		tspec.tv_sec = time(NULL);
		tspec.tv_nsec = 0;
	}

	memset(&tim, 0, sizeof(tim));
	localtime_r(&tspec.tv_sec, &tim);
	/* TODO: add millisecond to time string */
	rval = strftime(timp, tlen, "%b %e %T", &tim);
	if (rval == 0)
		timp[0] = '\0';
	else if (rval >= tlen)
		timp[tlen - 1] = '\0';
	return timp;
}
#endif

static int write_logbuf(char * p0, int l0, char * p1, int l1)
{
	int fd;
	ssize_t rval;
	struct iovec iov[2];
	struct lnxall_appinfo * appi;

	fd = -1;
	appi = &g_appinfo;
	iov[0].iov_base  = (void *) p0;
	iov[0].iov_len   = (size_t) l0;
	iov[1].iov_base  = (void *) p1;
	iov[1].iov_len   = (size_t) l1;

	__atomic_load(&(appi->logfd), &fd, __ATOMIC_ACQUIRE);
	rval = writev(fd, iov, 0x2);
	if (rval == -1) {
		int doc = 0;

		fd = lnxall_connect_logserver(NULL, 0);
		if (fd >= 0) {
			int oldfd = -1;
			__atomic_exchange(&(appi->logfd), &fd, &oldfd, __ATOMIC_SEQ_CST);
			if (oldfd >= 0 && oldfd != fd)
				close(oldfd);
		} else if (appi->logOpts & LOG_CONS) { /* from musl-1.1.6/src/misc/syslog.c */
			doc = -1;
			fd = lnxall_open_logfd(NULL, 0);
		}

		if (fd >= 0) {
			rval = writev(fd, iov, 0x2);
			if (doc)
				close(fd);
		}
	}
	if (rval != (ssize_t) (l0 + l1))
		return -1;
	return 0;
}

int lnxall_syslog(int loglevel, const char * logfmt, ...)
{
	va_list pva;
	int rval, error;
#if ADD_TIMESTR
	char buftime[32];
#endif
	char * logbuf, * prefix;
	int level, len_l, len_p;
	struct lnxall_appinfo * appi;

	rval = 0;
	error = 0;
	logbuf = NULL;
	prefix = NULL;
	len_l = len_p = 0;
	appi = &g_appinfo;

	/* check whether `loglevel is valid or not */
	if (loglevel < LNXALL_LOGEMERG || loglevel > LNXALL_LOGDEBUG) {
		rval = -1;
		fprintf(stderr, "Error, invalid log-level: %d\n", loglevel);
		fflush(stderr);
		goto get_lost;
	}

	/* filter log messages by comparing log-level */
	level = lnxall_loglevel_get();
	if (loglevel > level)
		goto get_lost;

	loglevel |= LOG_USER; /* see `man 3 syslog */

	/* check the validity of `logfmt */
	if (logfmt == NULL || logfmt[0] == '\0') {
		rval = -2;
		goto get_lost;
	}

	/* format the log buffer */
	va_start(pva, logfmt);
	len_l = vasprintf(&logbuf, logfmt, pva);
	va_end(pva);
	if (len_l <= 0 || logbuf == NULL) {
		rval = -3;
		error = errno;
		fprintf(stderr, "Error, failed to format '%s': %s\n",
			logfmt, strerror(error));
		fflush(stderr);
		goto get_lost;
	}

	/* format the log prefix */
#if ADD_TIMESTR
	get_timestr(buftime, sizeof(buftime));
	len_p = asprintf(&prefix, "<%d> %s [%d] ", loglevel, buftime,
		(int) appi->appPid);
#else
	len_p = asprintf(&prefix, "<%d> %s [%d] ", loglevel,
		appi->appName, (int) appi->appPid);
#endif
	if (len_p <= 0 || prefix == NULL) {
		rval = -4;
		error = errno;
		fprintf(stderr, "Error, failed to format log-prefix: %s\n",
			strerror(error));
		fflush(stderr);
		goto get_lost;
	}

	/*
	 * `logread from openwrt/ubox filters by checking whether
	 * a ' ' (space) character exists in `prefix by `SYSLOG_PADDING
	 * offset after priority field. We need to ensure that does not happen
	 * Refer to `ubox/log/syslog.c.
	 */
	do {
		int plen = (loglevel >= 10) ? 4 : 3;
		if ((len_p + len_l) >= (plen + SYSLOG_PADDING)) {
			char * where;
			int offset = plen + SYSLOG_PADDING - 1;
			if (offset >= len_p)
				where = &logbuf[offset - len_p];
			else
				where = &prefix[offset];
			if (*where == ' ')
				*where = '\t';
		}
	} while (0);

	if (write_logbuf(prefix, len_p, logbuf, len_l) < 0)
		rval = -5;

get_lost:
	if (logbuf != NULL)
		free(logbuf);
	if (prefix != NULL)
		free(prefix);
	if (error != 0)
		errno = error;
	return rval;
}

char * lnxall_bin2hex(const void * ptr_, size_t plen)
{
	int jdx;
	size_t idx;
	char * rval;
	const char * prefix;
	const unsigned char * ptr;

	prefix = "  [%02x]: ";
	ptr = (const unsigned char *) ptr_;
	if (ptr == NULL || plen == 0)
		return NULL;

	/*
	 * truncate binary buffer if larger than 128
	 * bytes. Refer to `libdyutils/src/include/dy_common.h
	 */
#define BIN2HEX_MAXSIZE 256
#define BIN2HEX_BUFSIZE (BIN2HEX_MAXSIZE * 4)
	if (plen > BIN2HEX_MAXSIZE)
		plen = BIN2HEX_MAXSIZE;

	rval = (char *) malloc(BIN2HEX_BUFSIZE + 1);
	if (rval == NULL)
		return NULL;

	jdx = snprintf(rval, BIN2HEX_BUFSIZE, prefix, 0x0);
	for (idx = 0; idx < plen;) {
		int len;
		len = snprintf(rval + jdx, (size_t) (BIN2HEX_BUFSIZE - jdx), "%02x ",
			(unsigned int) ptr[idx]);
		if (len > 0)
			jdx += len;

		idx++;
		if ((idx & 0xf) == 0) {
			rval[jdx++] = '\n';
			if (idx < plen) {
				len = snprintf(rval + jdx, (size_t) (BIN2HEX_BUFSIZE - jdx),
					prefix, (unsigned int) idx);
				if (len > 0)
					jdx += len;
			}
		} else if ((idx & 0x7) == 0) {
			rval[jdx++] = ' ';
			rval[jdx++] = ' ';
		}
	}

	if (jdx > 0 && rval[jdx - 1] != '\n')
		rval[jdx++] = '\n';
	rval[jdx++] = '\0';
	return rval;
}
