#define _GNU_SOURCE
#include <math.h>
#include <ctype.h>

#include <libubox/utils.h>
#include <libubox/usock.h>
#include <libubox/udebug-proto.h>
#include <libubox/udebug.h>
#include <ucode/module.h>
#include "udebug-pcap.h"

#define TRACE_RING_ID	0x7fffffff

static struct udebug u;
static bool init_done;
static int trace_dirfd = -1;

static struct udebug_hdr trace_hdr = {
	.format = UDEBUG_FORMAT_STRING,
};
static struct udebug_packet_info trace_meta = {
	.attr = {
		[UDEBUG_META_IFACE_NAME] = "kernel",
		[UDEBUG_META_IFACE_DESC] = "tracepoint events",
	},
};
static struct udebug_remote_buf trace_rbuf = {
	.node.key = (void *)(uintptr_t)TRACE_RING_ID,
	.buf.hdr = &trace_hdr,
	.meta = &trace_meta,
	.pcap_iface = ~0
};

struct uc_pcap {
	struct pcap_context pcap;
	FILE *f;
};

struct uc_remote_ring {
	struct udebug_remote_buf rb;
	struct udebug_packet_info info;
	uc_vm_t *vm;
};

struct uc_trace_ring {
	uc_vm_t *vm;
	uc_value_t *res;
	struct uloop_fd fd;
	int dir_fd;

	unsigned int ts_ofs;
	char *instance;
};

static void
uc_udebug_call_cb(uc_vm_t *vm, uc_value_t *this)
{
	uc_value_t *cb;

	if (!this)
		return;

	cb = ucv_resource_value_get(this, 0);
	if (!ucv_is_callable(cb))
		return;

	uc_vm_stack_push(vm, ucv_get(this));
	uc_vm_stack_push(vm, ucv_get(cb));
	if (uc_vm_call(vm, true, 0) != EXCEPTION_NONE)
		return;

	ucv_put(uc_vm_stack_pop(vm));
}

static void
uc_udebug_notify_cb(struct udebug *ctx, struct udebug_remote_buf *rb)
{
	struct uc_remote_ring *rr = container_of(rb, struct uc_remote_ring, rb);

	uc_udebug_call_cb(rr->vm, rb->priv);
}

static void
__uc_udebug_init(void)
{
	if (init_done)
		return;

	udebug_init(&u);
	avl_insert(&u.remote_rings, &trace_rbuf.node);
	u.notify_cb = uc_udebug_notify_cb;
	init_done = true;
}

static uc_value_t *
uc_udebug_init(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *arg = uc_fn_arg(0);
	uc_value_t *flag_auto = uc_fn_arg(1);
	const char *path = NULL;

	if (ucv_type(arg) == UC_STRING)
		path = ucv_string_get(arg);

	if (init_done && !path && udebug_is_connected(&u))
		goto out;

	__uc_udebug_init();
	if (flag_auto && !ucv_is_truish(flag_auto)) {
		if (udebug_connect(&u, path))
			return NULL;
	} else {
		udebug_auto_connect(&u, path);
	}

out:
	return ucv_boolean_new(true);
}

static struct udebug_remote_buf *
uc_fn_rbuf(uc_vm_t *vm)
{
	struct udebug_remote_buf *rb = uc_fn_thisval("udebug.rbuf");

	if (!rb || !rb->buf.data)
		return NULL;

	return rb;
}

static uc_value_t *
uc_udebug_get_ring(uc_vm_t *vm, size_t nargs)
{
	struct uc_remote_ring *rr;
	struct udebug_remote_buf *rb;
	struct udebug_packet_info *info;
	uc_value_t *arg = uc_fn_arg(0);
	uc_value_t *id, *proc, *name, *pid;
	size_t ifname_len, ifdesc_len, len;
	char *buf;
	uc_value_t *res;

#define R_IFACE_DESC	"%s:%d"

	if (ucv_type(arg) != UC_OBJECT)
		return NULL;

	id = ucv_object_get(arg, "id", NULL);
	proc = ucv_object_get(arg, "proc_name", NULL);
	name = ucv_object_get(arg, "ring_name", NULL);
	pid = ucv_object_get(arg, "pid", NULL);

	if (ucv_type(id) != UC_INTEGER ||
	    ucv_type(proc) != UC_STRING ||
	    ucv_type(name) != UC_STRING ||
	    ucv_type(pid) != UC_INTEGER)
		return NULL;

	ifname_len = strlen(ucv_string_get(name)) + 1;
	ifdesc_len = sizeof(R_IFACE_DESC) + strlen(ucv_string_get(proc)) + 10;
	len = ifname_len + ifdesc_len + sizeof(*rr);
	res = ucv_resource_create_ex(vm, "udebug.rbuf", (void **)&rr, 1, len);
	if (!res)
		return NULL;

	rr->vm = vm;
	rb = &rr->rb;
	info = &rr->info;
	rb->meta = info;
	rb->priv = res;
	buf = (char *)(rr + 1);

	strcpy(buf, ucv_string_get(name));
	info->attr[UDEBUG_META_IFACE_NAME] = buf;
	buf += ifname_len;

	snprintf(buf, ifdesc_len, R_IFACE_DESC,
	         ucv_string_get(proc), (unsigned int)ucv_int64_get(pid));
	info->attr[UDEBUG_META_IFACE_DESC] = buf;

	if (udebug_remote_buf_map(&u, rb, (uint32_t)ucv_int64_get(id))) {
		ucv_put(res);
		return NULL;
	}

	return res;
}

static uc_value_t *
uc_udebug_rbuf_fetch(uc_vm_t *vm, size_t nargs)
{
	struct udebug_remote_buf *rb = uc_fn_rbuf(vm);
	struct udebug_snapshot *s;

	if (!rb)
		return NULL;

	s = udebug_remote_buf_snapshot(rb);
	if (!s)
		return NULL;

	return ucv_resource_create(vm, "udebug.snapshot", s);
}

static uc_value_t *
uc_udebug_rbuf_set_poll_cb(uc_vm_t *vm, size_t nargs)
{
	struct udebug_remote_buf *rb = uc_fn_rbuf(vm);
	uc_value_t *res = _uc_fn_this_res(vm);
	uc_value_t *val = uc_fn_arg(0);

	if (!rb)
		return NULL;

	if (val && !ucv_is_callable(val))
		return NULL;

	ucv_resource_value_set(res, 0, ucv_get(val));

	if (rb->poll == !!val)
		goto out;

	if (!u.fd.registered)
		udebug_add_uloop(&u);
	udebug_remote_buf_set_poll(&u, rb, !!val);
	ucv_resource_persistent_set(res, !!val);
	if (val)
		ucv_get(rb->priv);
	else
		ucv_put(rb->priv);

out:
	return ucv_boolean_new(true);
}

static uc_value_t *
uc_udebug_rbuf_set_fetch_duration(uc_vm_t *vm, size_t nargs)
{
	struct udebug_remote_buf *rb = uc_fn_rbuf(vm);
	uc_value_t *val = uc_fn_arg(0);
	uint64_t ts;
	double t;

	if (!rb)
		return NULL;

	t = ucv_double_get(val);
	if (isnan(t))
		return NULL;

	ts = udebug_timestamp();
	ts -= (uint64_t)(fabs(t) * UDEBUG_TS_SEC);
	udebug_remote_buf_set_start_time(rb, ts);

	return ucv_boolean_new(true);
}

static uc_value_t *
uc_udebug_rbuf_set_fetch_count(uc_vm_t *vm, size_t nargs)
{
	struct udebug_remote_buf *rb = uc_fn_rbuf(vm);
	uc_value_t *val = uc_fn_arg(0);
	uint32_t count;

	if (!rb)
		return NULL;

	count = ucv_int64_get(val);
	udebug_remote_buf_set_start_offset(rb, count);

	return ucv_boolean_new(true);
}

static uc_value_t *
uc_udebug_rbuf_change_flags(uc_vm_t *vm, size_t nargs)
{
	struct udebug_remote_buf *rb = uc_fn_rbuf(vm);
	uc_value_t *mask = uc_fn_arg(0);
	uc_value_t *set = uc_fn_arg(1);

	if (!rb)
		return NULL;

	if (ucv_type(mask) != UC_INTEGER || ucv_type(set) != UC_INTEGER)
		return NULL;

	udebug_remote_buf_set_flags(rb, ucv_int64_get(mask), ucv_int64_get(set));
	return ucv_boolean_new(true);
}

static uc_value_t *
uc_udebug_rbuf_get_flags(uc_vm_t *vm, size_t nargs)
{
	struct udebug_remote_buf *rb = uc_fn_rbuf(vm);

	if (!rb)
		return NULL;

	return ucv_int64_new(udebug_buf_flags(&rb->buf));
}

static uc_value_t *
uc_udebug_rbuf_get_info(uc_vm_t *vm, size_t nargs)
{
	struct udebug_remote_buf *rb = uc_fn_rbuf(vm);
	uc_value_t *val;

	if (!rb)
		return NULL;

	val = ucv_object_new(vm);
	ucv_object_add(val, "ring_size", ucv_int64_new(rb->buf.hdr->ring_size));
	ucv_object_add(val, "data_size", ucv_int64_new(rb->buf.hdr->data_size));
	ucv_object_add(val, "format", ucv_int64_new(rb->buf.hdr->format));
	ucv_object_add(val, "sub_format", ucv_int64_new(rb->buf.hdr->sub_format));

	return val;
}

static uc_value_t *
uc_udebug_rbuf_close(uc_vm_t *vm, size_t nargs)
{
	struct udebug_remote_buf *rb = uc_fn_rbuf(vm);

	if (rb)
		udebug_remote_buf_unmap(&u, rb);

	return NULL;
}

static void
uc_udebug_pcap_init(struct uc_pcap *p, uc_value_t *args)
{
	uc_value_t *hw, *os, *app;
	struct pcap_meta meta = {};

	if (ucv_type(args) == UC_OBJECT) {
		hw = ucv_object_get(args, "hw", NULL);
		os = ucv_object_get(args, "os", NULL);
		app = ucv_object_get(args, "app", NULL);

		meta.hw = ucv_string_get(hw);
		meta.os = ucv_string_get(os);
		meta.app = ucv_string_get(app);
	}

	pcap_init(&p->pcap, &meta);
}

static uc_value_t *
uc_debug_pcap_init(uc_vm_t *vm, int fd, uc_value_t *args)
{
	struct uc_pcap *p;
	FILE *f;

	if (fd < 0)
		return NULL;

	f = fdopen(fd, "w");
	if (!f)
		return NULL;

	p = calloc(1, sizeof(*p));
	p->f = f;
	uc_udebug_pcap_init(p, args);
	pcap_block_write_file(p->f);

	return ucv_resource_create(vm, "udebug.pcap", p);
}

static uc_value_t *
uc_udebug_pcap_file(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *file = uc_fn_arg(0);
	uc_value_t *args = uc_fn_arg(1);
	int fd = -1;

	if (ucv_type(file) == UC_STRING) {
		fd = open(ucv_string_get(file), O_WRONLY | O_CREAT, 0644);
		if (ftruncate(fd, 0) < 0) {
			close(fd);
			return NULL;
		}
	} else if (!file)
		fd = STDOUT_FILENO;

	return uc_debug_pcap_init(vm, fd, args);
}

static uc_value_t *
uc_udebug_pcap_udp(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *host = uc_fn_arg(0);
	uc_value_t *port = uc_fn_arg(1);
	uc_value_t *args = uc_fn_arg(2);
	const char *port_str;
	int fd = -1;

	if (ucv_type(host) != UC_STRING)
		return NULL;

	if (ucv_type(port) == UC_STRING)
		port_str = ucv_string_get(port);
	else if (ucv_type(port) == UC_INTEGER)
		port_str = usock_port(ucv_int64_get(port));
	else
		return NULL;

	fd = usock(USOCK_UDP, ucv_string_get(host), port_str);

	return uc_debug_pcap_init(vm, fd, args);
}

static struct udebug_snapshot *
uc_get_snapshot(uc_value_t *val)
{
	return ucv_resource_data(val, "udebug.snapshot");
}

static uc_value_t *
uc_udebug_pcap_write(uc_vm_t *vm, size_t nargs)
{
	struct uc_pcap *p = uc_fn_thisval("udebug.pcap");
	uc_value_t *arg = uc_fn_arg(0);
	size_t n = ucv_type(arg) == UC_ARRAY ? ucv_array_length(arg) : 1;
	struct udebug_snapshot **s;
	struct udebug_iter it;
	bool ret = false;

	if (!p)
		return NULL;

	s = alloca(n * sizeof(*s));
	if (ucv_type(arg) == UC_ARRAY)
		for (size_t i = 0; i < n; i++) {
			if ((s[i] = uc_get_snapshot(ucv_array_get(arg, i))) == NULL)
				goto out;
	} else {
		if ((s[0] = uc_get_snapshot(arg)) == NULL)
			goto out;
	}

	udebug_iter_start(&it, s, n);
	while (udebug_iter_next(&it)) {
		struct udebug_remote_buf *rb;

		rb = udebug_remote_buf_get(&u, it.s->rbuf_idx);
		if (!pcap_interface_is_valid(&p->pcap, rb->pcap_iface)) {
			if (pcap_interface_rbuf_init(&p->pcap, rb))
				continue;

			if (!pcap_block_write_file(p->f))
				return NULL;
		}

		if (pcap_snapshot_packet_init(&u, &it))
			continue;

		if (!pcap_block_write_file(p->f))
			return NULL;

		ret = true;
	}

	if (ferror(p->f))
		return NULL;

out:
	return ucv_boolean_new(ret);
}

static void
uc_udebug_pcap_free(void *ptr)
{
	struct uc_pcap *p = ptr;

	if (!p)
		return;

	if (p->f)
		fclose(p->f);
	free(p);
}

static uc_value_t *
uc_udebug_pcap_close(uc_vm_t *vm, size_t nargs)
{
	void **p = uc_fn_this("udebug.pcap");

	if (!p)
		return NULL;

	uc_udebug_pcap_free(*p);
	*p = NULL;

	return NULL;
}

static uc_value_t *
uc_udebug_snapshot_get_ring(uc_vm_t *vm, size_t nargs)
{
	struct udebug_snapshot *s = uc_fn_thisval("udebug.snapshot");
	struct udebug_remote_buf *rb;

	if (!s)
		return NULL;

	rb = udebug_remote_buf_get(&u, s->rbuf_idx);
	if (!rb)
		return NULL;

	return ucv_get(rb->priv);
}

static uc_value_t *
uc_udebug_snapshot_entries(uc_vm_t *vm, size_t nargs)
{
	struct udebug_snapshot *s = uc_fn_thisval("udebug.snapshot");

	if (!s)
		return NULL;

	return ucv_int64_new(s->n_entries);
}

static uc_value_t *
uc_udebug_foreach_packet(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *arg = uc_fn_arg(0);
	uc_value_t *fn = uc_fn_arg(1);
	size_t n = ucv_type(arg) == UC_ARRAY ? ucv_array_length(arg) : 1;
	struct udebug_snapshot **s;
	struct udebug_iter it;

	if (!ucv_is_callable(fn))
		return NULL;

	s = alloca(n * sizeof(*s));
	if (ucv_type(arg) == UC_ARRAY)
		for (size_t i = 0; i < n; i++) {
			if ((s[i] = uc_get_snapshot(ucv_array_get(arg, i))) == NULL)
				return NULL;
	} else {
		if ((s[0] = uc_get_snapshot(arg)) == NULL)
			return NULL;
	}

	udebug_iter_start(&it, s, n);
	while (udebug_iter_next(&it)) {
		uc_value_t *s_obj;

		if (ucv_type(arg) == UC_ARRAY)
			s_obj = ucv_array_get(arg, it.s_idx);
		else
			s_obj = arg;

		uc_vm_stack_push(vm, ucv_get(_uc_fn_this_res(vm)));
		uc_vm_stack_push(vm, ucv_get(fn));
		uc_vm_stack_push(vm, ucv_get(s_obj));
		uc_vm_stack_push(vm, ucv_string_new_length(it.data, it.len));
		uc_vm_stack_push(vm, ucv_int64_new(it.timestamp));

		if (uc_vm_call(vm, true, 3) != EXCEPTION_NONE)
			break;

		ucv_put(uc_vm_stack_pop(vm));
	}

	return NULL;
}

static uc_value_t *
uc_udebug_create_ring(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *name, *flags_arr, *size, *entries, *format;
	uc_value_t *meta_obj = uc_fn_arg(0);
	struct udebug_buf_flag *flags;
	struct udebug_buf_meta *meta;
	struct udebug_buf *buf;
	size_t flag_str_len = 0;
	size_t flags_len = 0;
	char *name_buf, *flag_name_buf;

	if (ucv_type(meta_obj) != UC_OBJECT)
		return NULL;

	name = ucv_object_get(meta_obj, "name", NULL);
	flags_arr = ucv_object_get(meta_obj, "flags", NULL);
	size = ucv_object_get(meta_obj, "size", NULL);
	entries = ucv_object_get(meta_obj, "entries", NULL);

	if (ucv_type(name) != UC_STRING ||
	    ucv_type(size) != UC_INTEGER || ucv_type(entries) != UC_INTEGER)
		return NULL;

	if (ucv_type(flags_arr) == UC_ARRAY) {
		flags_len = ucv_array_length(flags_arr);
		for (size_t i = 0; i < flags_len; i++) {
			uc_value_t *f = ucv_array_get(flags_arr, i);
			if (ucv_type(f) != UC_STRING)
				return NULL;
			flag_str_len += strlen(ucv_string_get(f)) + 1;
		}
	}

	buf = calloc_a(sizeof(*buf),
		       &name_buf, strlen(ucv_string_get(name)) + 1,
		       &meta, sizeof(*meta),
		       &flags, flags_len * sizeof(*flags),
		       &flag_name_buf, flag_str_len);
	meta->name = strcpy(name_buf, ucv_string_get(name));
	meta->format = UDEBUG_FORMAT_STRING;
	meta->flags = flags;

	format = ucv_object_get(meta_obj, "format", NULL);
	if (ucv_type(format) == UC_INTEGER) {
		meta->format = UDEBUG_FORMAT_PACKET;
		meta->sub_format = ucv_int64_get(format);
	}

	for (size_t i = 0; i < flags_len; i++) {
		uc_value_t *f = ucv_array_get(flags_arr, i);
		const char *str = ucv_string_get(f);
		size_t len = strlen(str) + 1;

		flags->name = memcpy(name_buf, str, len);
		flags->mask = 1ULL << i;
		name_buf += len;
		meta->n_flags++;
	}

	if (udebug_buf_init(buf, ucv_int64_get(entries), ucv_int64_get(size))) {
		free(buf);
		return NULL;
	}

	udebug_buf_add(&u, buf, meta);

	return ucv_resource_create(vm, "udebug.wbuf", buf);
}

static void wbuf_free(void *ptr)
{
	if (!ptr)
		return;

	udebug_buf_free(ptr);
}

static uc_value_t *
uc_udebug_wbuf_flags(uc_vm_t *vm, size_t nargs)
{
	struct udebug_buf *buf = uc_fn_thisval("udebug.wbuf");

	if (!buf)
		return NULL;

	return ucv_int64_new(udebug_buf_flags(buf));
}

static uc_value_t *
uc_udebug_wbuf_close(uc_vm_t *vm, size_t nargs)
{
	void **p = uc_fn_this("udebug.wbuf");

	if (!p)
		return NULL;

	wbuf_free(*p);
	*p = NULL;

	return NULL;
}

static void
uc_udebug_wbuf_append(uc_vm_t *vm, struct udebug_buf *buf, uc_value_t *val)
{
	struct printbuf *pb;
	size_t len;

	if (!val)
		return;

	switch (ucv_type(val)) {
	case UC_STRING:
		udebug_entry_append(buf, ucv_string_get(val), ucv_string_length(val));
		break;
	case UC_ARRAY:
		len = ucv_array_length(val);
		for (size_t i = 0; i < len; i++)
			uc_udebug_wbuf_append(vm, buf, ucv_array_get(val, i));
		break;
	default:
		pb = xprintbuf_new();
		ucv_to_stringbuf(vm, pb, val, false);
		udebug_entry_append(buf, pb->buf, pb->bpos);
		printbuf_free(pb);
		break;
	}
}

static uc_value_t *
uc_udebug_wbuf_add(uc_vm_t *vm, size_t nargs)
{
	struct udebug_buf *buf = uc_fn_thisval("udebug.wbuf");
	uc_value_t *arg = uc_fn_arg(0);

	if (!buf || !arg)
		return NULL;

	udebug_entry_init(buf);
	uc_udebug_wbuf_append(vm, buf, arg);
	udebug_entry_add(buf);

	return ucv_boolean_new(true);
}

static void
uc_udebug_trace_uloop_cb(struct uloop_fd *fd, unsigned int events)
{
	struct uc_trace_ring *tr = container_of(fd, struct uc_trace_ring, fd);

	uc_udebug_call_cb(tr->vm, tr->res);
}

static bool
trace_parse_meta(struct uc_trace_ring *tr, int fd)
{
	FILE *f = fdopen(fd, "r");
	char str[256];
	char *cur;

	if (!f) {
		close(fd);
		return false;
	}

	while (fgets(str, sizeof(str), f)) {
		cur = strstr(str, " TIMESTAMP ");
		if (!cur)
			continue;

		while (cur[-1] == ' ' && cur[-2] != '|')
			cur--;

		tr->ts_ofs = cur - str;
		break;
	}

	fclose(f);

	return tr->ts_ofs != 0;
}

static uc_value_t *
uc_udebug_trace_ring(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *instance_arg = uc_fn_arg(0);
	const char *instance = "udebug";
	struct uc_trace_ring *tr;
	int dfd, fd, ret;
	uc_value_t *res;

	if (instance_arg) {
		if (ucv_type(instance_arg) != UC_STRING)
			return NULL;

		instance = ucv_string_get(instance_arg);
	}

	if (trace_dirfd < 0)
		trace_dirfd = open("/sys/kernel/debug/tracing/instances", O_RDONLY | O_DIRECTORY);
	if (trace_dirfd < 0)
		return NULL;

	ret = unlinkat(trace_dirfd, instance, AT_REMOVEDIR);
	ret = mkdirat(trace_dirfd, instance, 0700);
	if (ret < 0)
		return NULL;

	dfd = openat(trace_dirfd, instance, O_RDONLY | O_DIRECTORY);
	if (dfd < 0)
		return NULL;

	fd = openat(dfd, "trace_pipe", O_RDONLY);
	if (fd < 0)
		goto error;

	res = ucv_resource_create_ex(vm, "udebug.tbuf", (void **)&tr, 1, sizeof(*tr) + strlen(instance + 1));
	if (!res)
		goto error;

	__uc_udebug_init();
	tr->vm = vm;
	tr->dir_fd = dfd;
	tr->fd.fd = fd;
	tr->fd.cb = uc_udebug_trace_uloop_cb;
	tr->instance = (char *)(tr + 1);
	strcpy(tr->instance, instance);

	fd = openat(dfd, "trace", O_RDONLY);
	if (fd < 0 || !trace_parse_meta(tr, fd)) {
		ucv_put(res);
		return NULL;
	}

	return res;

error:
	close(dfd);
	return NULL;
}

static void tbuf_close(void *ptr)
{
	struct uc_trace_ring *tr = ptr;

	if (tr->fd.fd < 0)
		return;

	uloop_fd_delete(&tr->fd);
	close(tr->fd.fd);
	close(tr->dir_fd);
	tr->fd.fd = -1;
	unlinkat(trace_dirfd, tr->instance, AT_REMOVEDIR);
}

static struct uc_trace_ring *
uc_fn_tbuf(uc_vm_t *vm)
{
	struct uc_trace_ring *tr = uc_fn_thisval("udebug.tbuf");

	if (!tr || tr->fd.fd < 0)
		return NULL;

	return tr;
}

static uc_value_t *
uc_udebug_tbuf_set_file(uc_vm_t *vm, size_t nargs)
{
	struct uc_trace_ring *tr = uc_fn_tbuf(vm);
	uc_value_t *file = uc_fn_arg(0);
	uc_value_t *val = uc_fn_arg(1);
	uc_value_t *ret = NULL;
	FILE *f;
	int fd;

	if (!tr || ucv_type(file) != UC_STRING || ucv_type(val) != UC_STRING)
		return NULL;

	fd = openat(tr->dir_fd, ucv_string_get(file), O_WRONLY);
	if (fd < 0)
		return NULL;

	f = fdopen(fd, "w");
	if (!f) {
		close(fd);
		return NULL;
	}

	if (fwrite(ucv_string_get(val), ucv_string_length(val), 1, f) == 1)
		ret = ucv_boolean_new(true);

	fclose(f);

	return ret;
}


static uc_value_t *
uc_udebug_tbuf_set_poll_cb(uc_vm_t *vm, size_t nargs)
{
	struct uc_trace_ring *tr = uc_fn_tbuf(vm);
	uc_value_t *res = _uc_fn_this_res(vm);
	uc_value_t *val = uc_fn_arg(0);

	if (!tr)
		return NULL;

	if (val && !ucv_is_callable(val))
		return NULL;

	ucv_resource_value_set(res, 0, ucv_get(val));

	if (!!tr->res == !!val)
		goto out;

	ucv_resource_persistent_set(res, !!val);
	if (val) {
		uloop_fd_add(&tr->fd, ULOOP_READ);
		tr->res = ucv_get(res);
	} else {
		uloop_fd_delete(&tr->fd);
		ucv_put(tr->res);
		tr->res = NULL;
	}

out:
	return ucv_boolean_new(true);
}

static struct udebug_snapshot *
snapshot_realloc(struct udebug_snapshot *s, size_t data_size)
{
	s = xrealloc(s, sizeof(*s) + data_size);
	s->data = s + 1;
	return s;
}

static bool
trace_parse_timestamp(struct udebug_ptr *ptr, char *str)
{
	uint64_t ts, usec;
	char *err;

	ts = strtoul(str, &err, 10);
	if (*err != '.')
		return false;

	usec = strtoul(err + 1, &err, 10);
	if (*err != ':')
		return false;

	ptr->timestamp = ts * 1000000 + usec;
	memmove(str, err + 1, strlen(err + 1) + 1);

	return true;
}

static uc_value_t *
uc_udebug_tbuf_fetch(uc_vm_t *vm, size_t nargs)
{
	struct uc_trace_ring *tr = uc_fn_tbuf(vm);
	struct udebug_snapshot *s = NULL;
	struct udebug_ptr *ptr = NULL;
	size_t data_ofs = 0;
	size_t data_size = 0;
	size_t ptr_ofs = 0;
	size_t ptr_size = 0;
	char *cur, *next;
	size_t ofs = 0;
	char buf[8192];
	ssize_t len;

	if (!tr)
		return NULL;

read_again:
	len = read(tr->fd.fd, buf + ofs, sizeof(buf) - ofs);
	if (len < 0) {
		if (errno == EINTR)
			goto read_again;

		if (errno == EAGAIN || data_size)
			goto done;

		goto error;
	}
	len += ofs;

	cur = buf;
	while ((next = memchr(cur, '\n', len)) != NULL) {
		struct udebug_ptr *cur_ptr;
		char *line = cur;

		*next = 0;

		while (isspace(*line))
			line++;

		if (*line == '#' || !line[0])
			goto next_line;

		if (next - cur <= tr->ts_ofs)
			goto next_line;

		if (ptr_ofs >= ptr_size) {
			if (!ptr_size)
				ptr_size = 4;
			else
				ptr_size *= 2;

			ptr = xrealloc(ptr, ptr_size * sizeof(*ptr));
		}

		cur_ptr = &ptr[ptr_ofs];
		if (!trace_parse_timestamp(cur_ptr, cur + tr->ts_ofs))
			goto next_line;

		cur_ptr->start = data_ofs;
		cur_ptr->len = strlen(line);
		data_ofs += cur_ptr->len + 1;
		if (data_ofs >= data_size) {
			if (!data_size)
				data_size = 256;
			else
				data_size *= 2;
			s = snapshot_realloc(s, data_size);
		}
		strcpy(s->data + cur_ptr->start, line);
		ptr_ofs++;

next_line:
		len -= next + 1 - cur;
		cur = next + 1;
	}

	if (len)
		memmove(buf, cur, len);
	ofs = len;
	goto read_again;

done:
	if (!s)
		goto error;

	ptr_size = ptr_ofs * sizeof(*ptr);
	data_ofs = (data_ofs + 3) & ~3;
	data_size = data_ofs + ptr_size;
	s = snapshot_realloc(s, data_size);
	s->data_size = data_ofs;
	s->entries = memcpy(s->data + data_ofs, ptr, ptr_size);
	s->n_entries = ptr_ofs;
	s->rbuf_idx = TRACE_RING_ID;
	free(ptr);

	return ucv_resource_create(vm, "udebug.snapshot", s);

error:
	free(ptr);
	free(s);
	return NULL;
}

static uc_value_t *
uc_udebug_tbuf_close(uc_vm_t *vm, size_t nargs)
{
	struct uc_trace_ring *tr = uc_fn_tbuf(vm);

	if (!tr)
		return NULL;

	tbuf_close(tr);

	if (!tr->res)
		return NULL;

	ucv_resource_persistent_set(tr->res, false);
	ucv_put(tr->res);
	tr->res = NULL;

	return NULL;
}


static const uc_function_list_t pcap_fns[] = {
	{ "close", uc_udebug_pcap_close },
	{ "write", uc_udebug_pcap_write },
};

static const uc_function_list_t snapshot_fns[] = {
	{ "get_ring", uc_udebug_snapshot_get_ring },
	{ "entries", uc_udebug_snapshot_entries }
};

static const uc_function_list_t wbuf_fns[] = {
	{ "add", uc_udebug_wbuf_add },
	{ "flags", uc_udebug_wbuf_flags },
	{ "close", uc_udebug_wbuf_close },
};

static const uc_function_list_t tbuf_fns[] = {
	{ "set_poll_cb", uc_udebug_tbuf_set_poll_cb },
	{ "set_file", uc_udebug_tbuf_set_file },
	{ "fetch", uc_udebug_tbuf_fetch },
	{ "close", uc_udebug_tbuf_close },
};

static const uc_function_list_t rbuf_fns[] = {
	{ "set_poll_cb", uc_udebug_rbuf_set_poll_cb },
	{ "fetch", uc_udebug_rbuf_fetch },
	{ "change_flags", uc_udebug_rbuf_change_flags },
	{ "get_flags", uc_udebug_rbuf_get_flags },
	{ "set_fetch_duration", uc_udebug_rbuf_set_fetch_duration },
	{ "set_fetch_count", uc_udebug_rbuf_set_fetch_count },
	{ "get_info", uc_udebug_rbuf_get_info },
	{ "close", uc_udebug_rbuf_close },
};

static const uc_function_list_t global_fns[] = {
	{ "init", uc_udebug_init },
	{ "create_ring", uc_udebug_create_ring },
	{ "get_ring", uc_udebug_get_ring },
	{ "trace_ring", uc_udebug_trace_ring },
	{ "pcap_file", uc_udebug_pcap_file },
	{ "pcap_udp", uc_udebug_pcap_udp },
	{ "foreach_packet", uc_udebug_foreach_packet },
};

static void rbuf_free(void *ptr)
{
	struct udebug_remote_buf *rb = ptr;

	if (rb->buf.data)
		udebug_remote_buf_unmap(&u, rb);
}

void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
{
	uc_function_list_register(scope, global_fns);

#define ADD_CONST(name) \
	ucv_object_add(scope, #name, ucv_int64_new(UDEBUG_##name))
	ADD_CONST(FORMAT_PACKET);
	ADD_CONST(FORMAT_STRING);
	ADD_CONST(FORMAT_BLOBMSG);
	ADD_CONST(DLT_ETHERNET);
	ADD_CONST(DLT_PPP);
	ADD_CONST(DLT_IEEE_802_11);
	ADD_CONST(DLT_IEEE_802_11_RADIOTAP);
	ADD_CONST(DLT_NETLINK);

	uc_type_declare(vm, "udebug.wbuf", wbuf_fns, wbuf_free);
	uc_type_declare(vm, "udebug.rbuf", rbuf_fns, rbuf_free);
	uc_type_declare(vm, "udebug.tbuf", tbuf_fns, tbuf_close);
	uc_type_declare(vm, "udebug.snapshot", snapshot_fns, free);
	uc_type_declare(vm, "udebug.pcap", pcap_fns, uc_udebug_pcap_free);
}
