diff --git a/daemon/bencode.c b/daemon/bencode.c
index 3b8880eb7..9a0de8876 100644
--- a/daemon/bencode.c
+++ b/daemon/bencode.c
@@ -137,6 +137,9 @@ alloc:
 void bencode_buffer_free(bencode_buffer_t *buf) {
 	struct __bencode_buffer_piece *piece, *next;
 
+	if (!buf)
+		return;
+
 	for (piece = buf->pieces; piece; piece = next) {
 		next = piece->next;
 		BENCODE_FREE(piece);
diff --git a/daemon/call.c b/daemon/call.c
index 4c33a2805..363c3bd47 100644
--- a/daemon/call.c
+++ b/daemon/call.c
@@ -4766,7 +4766,7 @@ static void monologue_stop(struct call_monologue *ml, bool stop_media_subsribers
 // call must be locked in W.
 // unlocks the call and releases the reference prior to returning, even on error.
 int call_delete_branch(call_t *c, const str *branch,
-	const str *fromtag, const str *totag, ng_parser_ctx_t *ctx, int delete_delay)
+	const str *fromtag, const str *totag, ng_command_ctx_t *ctx, int delete_delay)
 {
 	struct call_monologue *ml;
 	int ret;
@@ -4894,7 +4894,7 @@ out:
 
 
 int call_delete_branch_by_id(const str *callid, const str *branch,
-	const str *fromtag, const str *totag, ng_parser_ctx_t *ctx, int delete_delay)
+	const str *fromtag, const str *totag, ng_command_ctx_t *ctx, int delete_delay)
 {
 	call_t *c = call_get(callid);
 	if (!c) {
diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c
index c8d6e6e5e..d6479a873 100644
--- a/daemon/call_interfaces.c
+++ b/daemon/call_interfaces.c
@@ -1986,11 +1986,12 @@ void call_ng_main_flags(const ng_parser_t *parser, str *key, parser_arg value, h
 	}
 }
 
-static void call_ng_process_flags(sdp_ng_flags *out, ng_parser_ctx_t *ctx, enum call_opmode opmode) {
+static void call_ng_process_flags(sdp_ng_flags *out, ng_command_ctx_t *ctx, enum call_opmode opmode) {
+	const ng_parser_t *parser = ctx->parser_ctx.parser;
 	call_ng_flags_init(out, opmode);
 	ctx->opmode = opmode;
 	ctx->flags = out;
-	ctx->parser->dict_iter(ctx->parser, ctx->req, call_ng_main_flags, out);
+	parser->dict_iter(parser, ctx->req, call_ng_main_flags, out);
 }
 
 static void ng_sdp_attr_manipulations_free(struct sdp_manipulations * array[__MT_MAX]) {
@@ -2109,7 +2110,7 @@ static enum basic_errors call_ng_basic_checks(sdp_ng_flags *flags, enum call_opm
 	return 0;
 }
 
-static const char *call_offer_answer_ng(ng_parser_ctx_t *ctx, enum call_opmode opmode, const char* addr,
+static const char *call_offer_answer_ng(ng_command_ctx_t *ctx, enum call_opmode opmode, const char* addr,
 		const endpoint_t *sin)
 {
 	const char *errstr;
@@ -2121,6 +2122,7 @@ static const char *call_offer_answer_ng(ng_parser_ctx_t *ctx, enum call_opmode o
 	int ret;
 	g_auto(sdp_ng_flags) flags;
 	parser_arg output = ctx->resp;
+	const ng_parser_t *parser = ctx->parser_ctx.parser;
 
 	call_ng_process_flags(&flags, ctx, opmode);
 
@@ -2139,7 +2141,7 @@ static const char *call_offer_answer_ng(ng_parser_ctx_t *ctx, enum call_opmode o
 
 	if (flags.loop_protect && sdp_is_duplicate(&parsed)) {
 		ilog(LOG_INFO, "Ignoring message as SDP has already been processed by us");
-		ctx->parser->dict_add_str(output, "sdp", &flags.sdp);
+		parser->dict_add_str(output, "sdp", &flags.sdp);
 		errstr = NULL;
 		goto out;
 	}
@@ -2236,7 +2238,7 @@ static const char *call_offer_answer_ng(ng_parser_ctx_t *ctx, enum call_opmode o
 		meta_write_sdp_after(recording, chopper->output,
 			       from_ml, opmode);
 
-		recording_response(recording, ctx->parser, output);
+		recording_response(recording, ctx->parser_ctx.parser, output);
 	}
 
 	dequeue_sdp_fragments(from_ml);
@@ -2264,21 +2266,21 @@ static const char *call_offer_answer_ng(ng_parser_ctx_t *ctx, enum call_opmode o
 		goto out;
 
 	if (chopper->output->len)
-		ctx->parser->dict_add_str(output, "sdp", &STR_LEN(chopper->output->str, chopper->output->len));
+		ctx->parser_ctx.parser->dict_add_str(output, "sdp", &STR_LEN(chopper->output->str, chopper->output->len));
 
 	errstr = NULL;
 out:
 	return errstr;
 }
 
-const char *call_offer_ng(ng_parser_ctx_t *ctx,
+const char *call_offer_ng(ng_command_ctx_t *ctx,
 		const char* addr,
 		const endpoint_t *sin)
 {
 	return call_offer_answer_ng(ctx, OP_OFFER, addr, sin);
 }
 
-const char *call_answer_ng(ng_parser_ctx_t *ctx) {
+const char *call_answer_ng(ng_command_ctx_t *ctx) {
 	return call_offer_answer_ng(ctx, OP_ANSWER, NULL, NULL);
 }
 
@@ -2289,26 +2291,27 @@ static void call_delete_flags(str *key, unsigned int idx, helper_arg arg) {
 	else if (!str_cmp(key, "discard-recording"))
 		fatal_discard[1] = true;
 }
-const char *call_delete_ng(ng_parser_ctx_t *ctx) {
+const char *call_delete_ng(ng_command_ctx_t *ctx) {
 	str fromtag, totag, viabranch, callid;
 	parser_arg flags;
 	bool fatal_discard[2] = {0};
 	int delete_delay;
 	parser_arg input = ctx->req;
 	parser_arg output = ctx->resp;
+	const ng_parser_t *parser = ctx->parser_ctx.parser;
 
-	if (!ctx->parser->dict_get_str(input, "call-id", &callid))
+	if (!parser->dict_get_str(input, "call-id", &callid))
 		return "No call-id in message";
-	ctx->parser->dict_get_str(input, "from-tag", &fromtag);
-	ctx->parser->dict_get_str(input, "to-tag", &totag);
-	ctx->parser->dict_get_str(input, "via-branch", &viabranch);
+	parser->dict_get_str(input, "from-tag", &fromtag);
+	parser->dict_get_str(input, "to-tag", &totag);
+	parser->dict_get_str(input, "via-branch", &viabranch);
 
-	flags = ctx->parser->dict_get_expect(input, "flags", BENCODE_LIST);
+	flags = parser->dict_get_expect(input, "flags", BENCODE_LIST);
 	if (flags.gen)
-		ctx->parser->list_iter(ctx->parser, flags, call_delete_flags, NULL, fatal_discard);
-	delete_delay = ctx->parser->dict_get_int_str(input, "delete-delay", -1);
+		parser->list_iter(parser, flags, call_delete_flags, NULL, fatal_discard);
+	delete_delay = parser->dict_get_int_str(input, "delete-delay", -1);
 	if (delete_delay == -1)
-		delete_delay = ctx->parser->dict_get_int_str(input, "delete delay", -1);
+		delete_delay = parser->dict_get_int_str(input, "delete delay", -1);
 
 	call_t *c = call_get(&callid);
 	if (!c)
@@ -2325,19 +2328,20 @@ const char *call_delete_ng(ng_parser_ctx_t *ctx) {
 err:
 	if (fatal_discard[0])
 		return "Call-ID not found or tags didn't match";
-	ctx->parser->dict_add_string(output, "warning", "Call-ID not found or tags didn't match");
+	parser->dict_add_string(output, "warning", "Call-ID not found or tags didn't match");
 	return NULL;
 }
 
-static void ng_stats(ng_parser_ctx_t *ctx, parser_arg dict, const char *dict_name,
+static void ng_stats(ng_command_ctx_t *ctx, parser_arg dict, const char *dict_name,
 		const struct stream_stats *s,
 		struct stream_stats *totals)
 {
 	if (ctx) {
-		parser_arg d = ctx->parser->dict_add_dict(dict, dict_name);
-		ctx->parser->dict_add_int(d, "packets", atomic64_get_na(&s->packets));
-		ctx->parser->dict_add_int(d, "bytes", atomic64_get_na(&s->bytes));
-		ctx->parser->dict_add_int(d, "errors", atomic64_get_na(&s->errors));
+		const ng_parser_t *parser = ctx->parser_ctx.parser;
+		parser_arg d = parser->dict_add_dict(dict, dict_name);
+		parser->dict_add_int(d, "packets", atomic64_get_na(&s->packets));
+		parser->dict_add_int(d, "bytes", atomic64_get_na(&s->bytes));
+		parser->dict_add_int(d, "errors", atomic64_get_na(&s->errors));
 	}
 	if (!totals)
 		return;
@@ -2377,7 +2381,7 @@ static void ng_stats_stream_ssrc(const ng_parser_t *parser, parser_arg dict,
 
 #define BF_PS(k, f) if (PS_ISSET(ps, f)) parser->list_add_string(flags, k)
 
-static void ng_stats_stream(ng_parser_ctx_t *ctx, parser_arg list, const struct packet_stream *ps,
+static void ng_stats_stream(ng_command_ctx_t *ctx, parser_arg list, const struct packet_stream *ps,
 		struct call_stats *totals)
 {
 	parser_arg dict = {0}, flags;
@@ -2386,7 +2390,7 @@ static void ng_stats_stream(ng_parser_ctx_t *ctx, parser_arg list, const struct
 	if (!ctx)
 		goto stats;
 
-	const ng_parser_t *parser = ctx->parser;
+	const ng_parser_t *parser = ctx->parser_ctx.parser;
 
 	dict = parser->list_add_dict(list);
 
@@ -2437,7 +2441,7 @@ stats:
 
 #define BF_M(k, f) if (MEDIA_ISSET(m, f)) parser->list_add_string(flags, k)
 
-static void ng_stats_media(ng_parser_ctx_t *ctx, parser_arg list, const struct call_media *m,
+static void ng_stats_media(ng_command_ctx_t *ctx, parser_arg list, const struct call_media *m,
 		struct call_stats *totals)
 {
 	parser_arg dict, streams = {0}, flags;
@@ -2447,7 +2451,7 @@ static void ng_stats_media(ng_parser_ctx_t *ctx, parser_arg list, const struct c
 	if (!ctx)
 		goto stats;
 
-	const ng_parser_t *parser = ctx->parser;
+	const ng_parser_t *parser = ctx->parser_ctx.parser;
 
 	rtp_pt = __rtp_stats_codec((struct call_media *)m);
 
@@ -2501,7 +2505,7 @@ stats:
 	}
 }
 
-static void ng_stats_monologue(ng_parser_ctx_t *ctx, parser_arg dict, const struct call_monologue *ml,
+static void ng_stats_monologue(ng_command_ctx_t *ctx, parser_arg dict, const struct call_monologue *ml,
 		struct call_stats *totals, parser_arg ssrc)
 {
 	parser_arg sub, medias = {0};
@@ -2515,7 +2519,7 @@ static void ng_stats_monologue(ng_parser_ctx_t *ctx, parser_arg dict, const stru
 	if (!ctx)
 		goto stats;
 
-	const ng_parser_t *parser = ctx->parser;
+	const ng_parser_t *parser = ctx->parser_ctx.parser;
 
 	if (ml->tag.len)
 		sub = parser->dict_add_dict(dict, ml->tag.s);
@@ -2677,7 +2681,7 @@ static void ng_stats_ssrc(const ng_parser_t *parser, parser_arg dict, struct ssr
 }
 
 /* call must be locked */
-void ng_call_stats(ng_parser_ctx_t *ctx, call_t *call, const str *fromtag, const str *totag,
+void ng_call_stats(ng_command_ctx_t *ctx, call_t *call, const str *fromtag, const str *totag,
 		struct call_stats *totals)
 {
 	parser_arg tags = {0}, dict;
@@ -2685,6 +2689,7 @@ void ng_call_stats(ng_parser_ctx_t *ctx, call_t *call, const str *fromtag, const
 	struct call_monologue *ml;
 	struct call_stats t_b;
 	parser_arg ssrc = {0};
+	const ng_parser_t *parser = ctx->parser_ctx.parser;
 
 	if (!totals)
 		totals = &t_b;
@@ -2695,13 +2700,13 @@ void ng_call_stats(ng_parser_ctx_t *ctx, call_t *call, const str *fromtag, const
 
 	call_ngb_hold_ref(call, ctx->ngbuf);
 
-	ctx->parser->dict_add_int(ctx->resp, "created", call->created.tv_sec);
-	ctx->parser->dict_add_int(ctx->resp, "created_us", call->created.tv_usec);
-	ctx->parser->dict_add_int(ctx->resp, "last signal", call->last_signal);
-	ctx->parser->dict_add_int(ctx->resp, "last redis update", atomic64_get_na(&call->last_redis_update));
+	parser->dict_add_int(ctx->resp, "created", call->created.tv_sec);
+	parser->dict_add_int(ctx->resp, "created_us", call->created.tv_usec);
+	parser->dict_add_int(ctx->resp, "last signal", call->last_signal);
+	parser->dict_add_int(ctx->resp, "last redis update", atomic64_get_na(&call->last_redis_update));
 
-	ssrc = ctx->parser->dict_add_dict(ctx->resp, "SSRC");
-	tags = ctx->parser->dict_add_dict(ctx->resp, "tags");
+	ssrc = parser->dict_add_dict(ctx->resp, "SSRC");
+	tags = parser->dict_add_dict(ctx->resp, "tags");
 
 stats:
 	match_tag = (totag && totag->s && totag->len) ? totag : fromtag;
@@ -2740,26 +2745,27 @@ stats:
 	if (!ctx)
 		return;
 
-	dict = ctx->parser->dict_add_dict(ctx->resp, "totals");
+	dict = parser->dict_add_dict(ctx->resp, "totals");
 	ng_stats(ctx, dict, "RTP", &totals->totals[0], NULL);
 	ng_stats(ctx, dict, "RTCP", &totals->totals[1], NULL);
 
 	if (call->recording) {
-		parser_arg rec = ctx->parser->dict_add_dict(ctx->resp, "recording");
-		ctx->parser->dict_add_int(rec, "call recording", !!CALL_ISSET(call, RECORDING_ON));
-		ctx->parser->dict_add_int(rec, "forwarding", !!CALL_ISSET(call, REC_FORWARDING));
+		parser_arg rec = parser->dict_add_dict(ctx->resp, "recording");
+		parser->dict_add_int(rec, "call recording", !!CALL_ISSET(call, RECORDING_ON));
+		parser->dict_add_int(rec, "forwarding", !!CALL_ISSET(call, REC_FORWARDING));
 	}
 }
 
-static void ng_list_calls(ng_parser_ctx_t *ctx, parser_arg output, long long int limit) {
+static void ng_list_calls(ng_command_ctx_t *ctx, parser_arg output, long long int limit) {
 	rtpe_calls_ht_iter iter;
+	const ng_parser_t *parser = ctx->parser_ctx.parser;
 
 	rwlock_lock_r(&rtpe_callhash_lock);
 
 	t_hash_table_iter_init (&iter, rtpe_callhash);
 	str *key;
 	while (limit-- && t_hash_table_iter_next (&iter, &key, NULL)) {
-		ctx->parser->list_add_str_dup(output, key);
+		parser->list_add_str_dup(output, key);
 	}
 
 	rwlock_unlock_r(&rtpe_callhash_lock);
@@ -2767,18 +2773,19 @@ static void ng_list_calls(ng_parser_ctx_t *ctx, parser_arg output, long long int
 
 
 
-const char *call_query_ng(ng_parser_ctx_t *ctx) {
+const char *call_query_ng(ng_command_ctx_t *ctx) {
 	str callid, fromtag, totag;
 	call_t *call;
 	parser_arg input = ctx->req;
+	const ng_parser_t *parser = ctx->parser_ctx.parser;
 
-	if (!ctx->parser->dict_get_str(input, "call-id", &callid))
+	if (!parser->dict_get_str(input, "call-id", &callid))
 		return "No call-id in message";
 	call = call_get_opmode(&callid, OP_QUERY);
 	if (!call)
 		return "Unknown call-id";
-	ctx->parser->dict_get_str(input, "from-tag", &fromtag);
-	ctx->parser->dict_get_str(input, "to-tag", &totag);
+	parser->dict_get_str(input, "from-tag", &fromtag);
+	parser->dict_get_str(input, "to-tag", &totag);
 
 	ng_call_stats(ctx, call, &fromtag, &totag, NULL);
 	rwlock_unlock_w(&call->master_lock);
@@ -2788,18 +2795,19 @@ const char *call_query_ng(ng_parser_ctx_t *ctx) {
 }
 
 
-const char *call_list_ng(ng_parser_ctx_t *ctx) {
+const char *call_list_ng(ng_command_ctx_t *ctx) {
 	parser_arg calls;
 	long long int limit;
 	parser_arg input = ctx->req;
 	parser_arg output = ctx->resp;
+	const ng_parser_t *parser = ctx->parser_ctx.parser;
 
-	limit = ctx->parser->dict_get_int_str(input, "limit", 32);
+	limit = parser->dict_get_int_str(input, "limit", 32);
 
 	if (limit < 0) {
 		return "invalid limit, must be >= 0";
 	}
-	calls = ctx->parser->dict_add_list(output, "calls");
+	calls = parser->dict_add_list(output, "calls");
 
 	ng_list_calls(ctx, calls, limit);
 
@@ -2807,17 +2815,18 @@ const char *call_list_ng(ng_parser_ctx_t *ctx) {
 }
 
 
-static const char *call_recording_common_ng(ng_parser_ctx_t *ctx,
+static const char *call_recording_common_ng(ng_command_ctx_t *ctx,
 		enum call_opmode opmode,
-		void (*fn)(ng_parser_ctx_t *, call_t *call))
+		void (*fn)(ng_command_ctx_t *, call_t *call))
 {
 	g_auto(sdp_ng_flags) flags;
 	g_autoptr(call_t) call = NULL;
 	parser_arg input = ctx->req;
+	const ng_parser_t *parser = ctx->parser_ctx.parser;
 
 	call_ng_process_flags(&flags, ctx, opmode);
 
-	if (!ctx->parser->dict_get_str(input, "call-id", &flags.call_id))
+	if (!parser->dict_get_str(input, "call-id", &flags.call_id))
 		return "No call-id in message";
 	call = call_get_opmode(&flags.call_id, opmode);
 	if (!call)
@@ -2825,7 +2834,7 @@ static const char *call_recording_common_ng(ng_parser_ctx_t *ctx,
 
 	struct call_monologue *ml = NULL;
 
-	if (ctx->parser->dict_get_str(input, "from-tag", &flags.from_tag)) {
+	if (parser->dict_get_str(input, "from-tag", &flags.from_tag)) {
 		if (flags.from_tag.s) {
 			ml = call_get_monologue(call, &flags.from_tag);
 			if (!ml)
@@ -2845,18 +2854,18 @@ static const char *call_recording_common_ng(ng_parser_ctx_t *ctx,
 }
 
 
-static void start_recording_fn(ng_parser_ctx_t *ctx, call_t *call) {
+static void start_recording_fn(ng_command_ctx_t *ctx, call_t *call) {
 	recording_start(call);
 }
-const char *call_start_recording_ng(ng_parser_ctx_t *ctx) {
+const char *call_start_recording_ng(ng_command_ctx_t *ctx) {
 	return call_recording_common_ng(ctx, OP_START_RECORDING, start_recording_fn);
 }
 
 
-static void pause_recording_fn(ng_parser_ctx_t *ctx, call_t *call) {
+static void pause_recording_fn(ng_command_ctx_t *ctx, call_t *call) {
 	recording_pause(call);
 }
-const char *call_pause_recording_ng(ng_parser_ctx_t *ctx) {
+const char *call_pause_recording_ng(ng_command_ctx_t *ctx) {
 	return call_recording_common_ng(ctx, OP_PAUSE_RECORDING, pause_recording_fn);
 }
 
@@ -2867,25 +2876,26 @@ static void stop_recording_iter(str *key, unsigned int idx, helper_arg arg) {
 	else if (str_cmp(key, "discard-recording") == 0)
 		*arg.call_fn = recording_discard;
 }
-static void stop_recording_fn(ng_parser_ctx_t *ctx, call_t *call) {
+static void stop_recording_fn(ng_command_ctx_t *ctx, call_t *call) {
 	// support alternative usage for "pause" call: either `pause=yes` ...
 	parser_arg input = ctx->req;
+	const ng_parser_t *parser = ctx->parser_ctx.parser;
 	str pause;
-	if (ctx->parser->dict_get_str(input, "pause", &pause)) {
+	if (parser->dict_get_str(input, "pause", &pause)) {
 		if (!str_cmp(&pause, "yes") || !str_cmp(&pause, "on") || !str_cmp(&pause, "true")) {
 			pause_recording_fn(ctx, call);
 			return;
 		}
 	}
 	// ... or `flags=[pause]`
-	parser_arg item = ctx->parser->dict_get_expect(input, "flags", BENCODE_LIST);
+	parser_arg item = parser->dict_get_expect(input, "flags", BENCODE_LIST);
 	void (*fn)(call_t *) = recording_stop;
 	if (item.gen)
-		ctx->parser->list_iter(ctx->parser, item, stop_recording_iter, NULL, &fn);
+		parser->list_iter(parser, item, stop_recording_iter, NULL, &fn);
 
 	fn(call);
 }
-const char *call_stop_recording_ng(ng_parser_ctx_t *ctx) {
+const char *call_stop_recording_ng(ng_command_ctx_t *ctx) {
 	return call_recording_common_ng(ctx, OP_STOP_RECORDING, stop_recording_fn);
 }
 
@@ -2936,7 +2946,7 @@ found:
 	return NULL;
 }
 static const char *media_block_match(call_t **call, struct call_monologue **monologue,
-		sdp_ng_flags *flags, ng_parser_ctx_t *ctx, enum call_opmode opmode)
+		sdp_ng_flags *flags, ng_command_ctx_t *ctx, enum call_opmode opmode)
 {
 	*call = NULL;
 	*monologue = NULL;
@@ -2972,7 +2982,7 @@ void add_media_to_sub_list(subscription_q *q, struct call_media *media, struct c
 	t_queue_push_tail(q, ms);
 }
 static const char *media_block_match_mult(call_t **call, subscription_q *medias,
-		sdp_ng_flags *flags, ng_parser_ctx_t *ctx, enum call_opmode opmode)
+		sdp_ng_flags *flags, ng_command_ctx_t *ctx, enum call_opmode opmode)
 {
 	call_ng_process_flags(flags, ctx, opmode);
 
@@ -3036,7 +3046,7 @@ static const char *media_block_match_mult(call_t **call, subscription_q *medias,
 }
 
 // XXX these are all identical - unify and use a flags int and/or callback
-const char *call_start_forwarding_ng(ng_parser_ctx_t *ctx) {
+const char *call_start_forwarding_ng(ng_command_ctx_t *ctx) {
 	g_autoptr(call_t) call = NULL;
 	struct call_monologue *monologue;
 	const char *errstr = NULL;
@@ -3065,7 +3075,7 @@ const char *call_start_forwarding_ng(ng_parser_ctx_t *ctx) {
 	return NULL;
 }
 
-const char *call_stop_forwarding_ng(ng_parser_ctx_t *ctx) {
+const char *call_stop_forwarding_ng(ng_command_ctx_t *ctx) {
 	g_autoptr(call_t) call = NULL;
 	struct call_monologue *monologue;
 	const char *errstr = NULL;
@@ -3173,7 +3183,7 @@ static void call_set_dtmf_block(call_t *call, struct call_monologue *monologue,
 	}
 
 }
-const char *call_block_dtmf_ng(ng_parser_ctx_t *ctx) {
+const char *call_block_dtmf_ng(ng_command_ctx_t *ctx) {
 	g_autoptr(call_t) call = NULL;
 	struct call_monologue *monologue;
 	const char *errstr = NULL;
@@ -3188,7 +3198,7 @@ const char *call_block_dtmf_ng(ng_parser_ctx_t *ctx) {
 	return NULL;
 }
 
-const char *call_unblock_dtmf_ng(ng_parser_ctx_t *ctx) {
+const char *call_unblock_dtmf_ng(ng_command_ctx_t *ctx) {
 	g_autoptr(call_t) call = NULL;
 	struct call_monologue *monologue;
 	const char *errstr = NULL;
@@ -3247,7 +3257,7 @@ const char *call_unblock_dtmf_ng(ng_parser_ctx_t *ctx) {
 	return NULL;
 }
 
-static const char *call_block_silence_media(ng_parser_ctx_t *ctx, bool on_off, const char *ucase_verb,
+static const char *call_block_silence_media(ng_command_ctx_t *ctx, bool on_off, const char *ucase_verb,
 		const char *lcase_verb,
 		unsigned int call_flag, unsigned int ml_flag, size_t attr_offset)
 {
@@ -3412,23 +3422,23 @@ static const char *call_block_silence_media(ng_parser_ctx_t *ctx, bool on_off, c
 			ML_FLAG_ ## flag, \
 			G_STRUCT_OFFSET(struct sink_attrs, member_name))
 
-const char *call_block_media_ng(ng_parser_ctx_t *ctx) {
+const char *call_block_media_ng(ng_command_ctx_t *ctx) {
 	return CALL_BLOCK_SILENCE_MEDIA(ctx, true, "Blocking", "blocking", block_media, BLOCK_MEDIA);
 }
-const char *call_unblock_media_ng(ng_parser_ctx_t *ctx) {
+const char *call_unblock_media_ng(ng_command_ctx_t *ctx) {
 	return CALL_BLOCK_SILENCE_MEDIA(ctx, false, "Unblocking", "unblocking", block_media, BLOCK_MEDIA);
 }
-const char *call_silence_media_ng(ng_parser_ctx_t *ctx) {
+const char *call_silence_media_ng(ng_command_ctx_t *ctx) {
 	return CALL_BLOCK_SILENCE_MEDIA(ctx, true, "Silencing", "silencing", silence_media, SILENCE_MEDIA);
 }
-const char *call_unsilence_media_ng(ng_parser_ctx_t *ctx) {
+const char *call_unsilence_media_ng(ng_command_ctx_t *ctx) {
 	return CALL_BLOCK_SILENCE_MEDIA(ctx, false, "Unsilencing", "unsilencing", silence_media, SILENCE_MEDIA);
 }
 
 
 #ifdef WITH_TRANSCODING
 static const char *play_media_select_party(call_t **call, monologues_q *monologues,
-		ng_parser_ctx_t *ctx, sdp_ng_flags *flags)
+		ng_command_ctx_t *ctx, sdp_ng_flags *flags)
 {
 	struct call_monologue *monologue;
 
@@ -3448,12 +3458,13 @@ static const char *play_media_select_party(call_t **call, monologues_q *monologu
 #endif
 
 
-const char *call_play_media_ng(ng_parser_ctx_t *ctx) {
+const char *call_play_media_ng(ng_command_ctx_t *ctx) {
 #ifdef WITH_TRANSCODING
 	g_autoptr(call_t) call = NULL;
 	g_auto(monologues_q) monologues;
 	const char *err = NULL;
 	g_auto(sdp_ng_flags) flags;
+	const ng_parser_t *parser = ctx->parser_ctx.parser;
 
 	err = play_media_select_party(&call, &monologues, ctx, &flags);
 	if (err)
@@ -3496,7 +3507,7 @@ const char *call_play_media_ng(ng_parser_ctx_t *ctx) {
 			return "No media file specified";
 
 		if (l == monologues.head && monologue->player->coder.duration)
-			ctx->parser->dict_add_int(ctx->resp, "duration", monologue->player->coder.duration);
+			parser->dict_add_int(ctx->resp, "duration", monologue->player->coder.duration);
 
 	}
 
@@ -3507,13 +3518,14 @@ const char *call_play_media_ng(ng_parser_ctx_t *ctx) {
 }
 
 
-const char *call_stop_media_ng(ng_parser_ctx_t *ctx) {
+const char *call_stop_media_ng(ng_command_ctx_t *ctx) {
 #ifdef WITH_TRANSCODING
 	g_autoptr(call_t) call = NULL;
 	g_auto(monologues_q) monologues;
 	const char *err = NULL;
 	long long last_frame_pos = 0;
 	g_auto(sdp_ng_flags) flags;
+	const ng_parser_t *parser = ctx->parser_ctx.parser;
 
 	err = play_media_select_party(&call, &monologues, ctx, &flags);
 	if (err)
@@ -3531,7 +3543,7 @@ const char *call_stop_media_ng(ng_parser_ctx_t *ctx) {
 		codec_update_all_source_handlers(monologue, NULL);
 		update_init_subscribers(monologue, OP_STOP_MEDIA);
 	}
-	ctx->parser->dict_add_int(ctx->resp, "last-frame-pos", last_frame_pos);
+	parser->dict_add_int(ctx->resp, "last-frame-pos", last_frame_pos);
 
 	return NULL;
 #else
@@ -3540,7 +3552,7 @@ const char *call_stop_media_ng(ng_parser_ctx_t *ctx) {
 }
 
 
-const char *call_play_dtmf_ng(ng_parser_ctx_t *ctx) {
+const char *call_play_dtmf_ng(ng_command_ctx_t *ctx) {
 #ifdef WITH_TRANSCODING
 	g_autoptr(call_t) call = NULL;
 	g_auto(monologues_q) monologues;
@@ -3642,7 +3654,7 @@ found_sink:
 }
 
 
-const char *call_publish_ng(ng_parser_ctx_t *ctx,
+const char *call_publish_ng(ng_command_ctx_t *ctx,
 		const char *addr,
 		const endpoint_t *sin)
 {
@@ -3653,6 +3665,7 @@ const char *call_publish_ng(ng_parser_ctx_t *ctx,
 	g_auto(str) sdp_out = STR_NULL;
 	g_autoptr(call_t) call = NULL;
 	int ret;
+	const ng_parser_t *parser = ctx->parser_ctx.parser;
 
 	call_ng_process_flags(&flags, ctx, OP_PUBLISH);
 
@@ -3682,7 +3695,7 @@ const char *call_publish_ng(ng_parser_ctx_t *ctx,
 	if (!ret) {
 		save_last_sdp(ml, &sdp_in, &parsed, &streams);
 		ctx->ngbuf->sdp_out = sdp_out.s;
-		ctx->parser->dict_add_str(ctx->resp, "sdp", &sdp_out);
+		parser->dict_add_str(ctx->resp, "sdp", &sdp_out);
 		sdp_out = STR_NULL; // ownership passed to output
 	}
 
@@ -3697,7 +3710,7 @@ const char *call_publish_ng(ng_parser_ctx_t *ctx,
 }
 
 
-const char *call_subscribe_request_ng(ng_parser_ctx_t *ctx) {
+const char *call_subscribe_request_ng(ng_command_ctx_t *ctx) {
 	const char *err = NULL;
 	g_auto(sdp_ng_flags) flags;
 	char rand_buf[65];
@@ -3705,6 +3718,7 @@ const char *call_subscribe_request_ng(ng_parser_ctx_t *ctx) {
 	g_auto(subscription_q) srms = TYPED_GQUEUE_INIT;
 	g_auto(str) sdp_out = STR_NULL;
 	parser_arg output = ctx->resp;
+	const ng_parser_t *parser = ctx->parser_ctx.parser;
 
 	/* get source monologue */
 	err = media_block_match_mult(&call, &srms, &flags, ctx, OP_REQUEST);
@@ -3745,7 +3759,7 @@ const char *call_subscribe_request_ng(ng_parser_ctx_t *ctx) {
 	/* place return output SDP */
 	if (sdp_out.len) {
 		ctx->ngbuf->sdp_out = sdp_out.s;
-		ctx->parser->dict_add_str(output, "sdp", &sdp_out);
+		parser->dict_add_str(output, "sdp", &sdp_out);
 		sdp_out = STR_NULL; /* ownership passed to output */
 	}
 
@@ -3755,49 +3769,49 @@ const char *call_subscribe_request_ng(ng_parser_ctx_t *ctx) {
 	if (srms.length == 1) {
 		struct media_subscription *ms = srms.head->data;
 		struct call_monologue *source_ml = ms->monologue;
-		ctx->parser->dict_add_str_dup(output, "from-tag", &source_ml->tag);
+		parser->dict_add_str_dup(output, "from-tag", &source_ml->tag);
 	}
 	parser_arg tag_medias = {0}, media_labels = {0};
 	if (flags.siprec) {
-		tag_medias = ctx->parser->dict_add_list(output, "tag-medias");
-		media_labels = ctx->parser->dict_add_dict(output, "media-labels");
+		tag_medias = parser->dict_add_list(output, "tag-medias");
+		media_labels = parser->dict_add_dict(output, "media-labels");
 	}
-	parser_arg from_list = ctx->parser->dict_add_list(output, "from-tags");
+	parser_arg from_list = parser->dict_add_list(output, "from-tags");
 	for (__auto_type l = srms.head; l; l = l->next) {
 		struct media_subscription *ms = l->data;
 		struct call_monologue *source_ml = ms->monologue;
-		ctx->parser->list_add_str_dup(from_list, &source_ml->tag);
+		parser->list_add_str_dup(from_list, &source_ml->tag);
 		if (tag_medias.gen) {
-			parser_arg tag_label = ctx->parser->list_add_dict(tag_medias);
-			ctx->parser->dict_add_str(tag_label, "tag", &source_ml->tag);
+			parser_arg tag_label = parser->list_add_dict(tag_medias);
+			parser->dict_add_str(tag_label, "tag", &source_ml->tag);
 			if (source_ml->label.len)
-				ctx->parser->dict_add_str(tag_label, "label", &source_ml->label);
-			parser_arg medias = ctx->parser->dict_add_list(tag_label, "medias");
+				parser->dict_add_str(tag_label, "label", &source_ml->label);
+			parser_arg medias = parser->dict_add_list(tag_label, "medias");
 			for (unsigned int i = 0; i < source_ml->medias->len; i++) {
 				struct call_media *media = source_ml->medias->pdata[i];
 				if (!media)
 					continue;
-				parser_arg med_ent = ctx->parser->list_add_dict(medias);
-				ctx->parser->dict_add_int(med_ent, "index", media->index);
-				ctx->parser->dict_add_str(med_ent, "type", &media->type);
-				ctx->parser->dict_add_str(med_ent, "label", &media->label);
-				ctx->parser->dict_add_string(med_ent, "mode", sdp_get_sendrecv(media));
+				parser_arg med_ent = parser->list_add_dict(medias);
+				parser->dict_add_int(med_ent, "index", media->index);
+				parser->dict_add_str(med_ent, "type", &media->type);
+				parser->dict_add_str(med_ent, "label", &media->label);
+				parser->dict_add_string(med_ent, "mode", sdp_get_sendrecv(media));
 
 				if (media_labels.gen) {
 					parser_arg label =
-						ctx->parser->dict_add_dict(media_labels, media->label.s);
-					ctx->parser->dict_add_str(label, "tag", &source_ml->tag);
-					ctx->parser->dict_add_int(label, "index", media->index);
-					ctx->parser->dict_add_str(label, "type", &media->type);
+						parser->dict_add_dict(media_labels, media->label.s);
+					parser->dict_add_str(label, "tag", &source_ml->tag);
+					parser->dict_add_int(label, "index", media->index);
+					parser->dict_add_str(label, "type", &media->type);
 					if (source_ml->label.len)
-						ctx->parser->dict_add_str(label, "label", &source_ml->label);
-					ctx->parser->dict_add_string(label, "mode", sdp_get_sendrecv(media));
+						parser->dict_add_str(label, "label", &source_ml->label);
+					parser->dict_add_string(label, "mode", sdp_get_sendrecv(media));
 				}
 			}
 		}
 	}
 
-	ctx->parser->dict_add_str_dup(output, "to-tag", &dest_ml->tag);
+	parser->dict_add_str_dup(output, "to-tag", &dest_ml->tag);
 
 	dequeue_sdp_fragments(dest_ml);
 
@@ -3807,7 +3821,7 @@ const char *call_subscribe_request_ng(ng_parser_ctx_t *ctx) {
 }
 
 
-const char *call_subscribe_answer_ng(ng_parser_ctx_t *ctx) {
+const char *call_subscribe_answer_ng(ng_command_ctx_t *ctx) {
 	g_auto(sdp_ng_flags) flags;
 	g_auto(sdp_sessions_q) parsed = TYPED_GQUEUE_INIT;
 	g_auto(sdp_streams_q) streams = TYPED_GQUEUE_INIT;
@@ -3849,7 +3863,7 @@ const char *call_subscribe_answer_ng(ng_parser_ctx_t *ctx) {
 }
 
 
-const char *call_unsubscribe_ng(ng_parser_ctx_t *ctx) {
+const char *call_unsubscribe_ng(ng_command_ctx_t *ctx) {
 	g_auto(sdp_ng_flags) flags;
 	g_autoptr(call_t) call = NULL;
 
diff --git a/daemon/control_ng.c b/daemon/control_ng.c
index 74ab15b58..0080c071b 100644
--- a/daemon/control_ng.c
+++ b/daemon/control_ng.c
@@ -133,10 +133,10 @@ static long long bencode_get_int(bencode_item_t *arg) {
 	return arg->value;
 }
 static parser_arg __bencode_dict(ng_parser_ctx_t *ctx) {
-	return (parser_arg) bencode_dictionary(&ctx->ngbuf->buffer);
+	return (parser_arg) bencode_dictionary(ctx->buffer);
 }
 static parser_arg __bencode_list(ng_parser_ctx_t *ctx) {
-	return (parser_arg) bencode_list(&ctx->ngbuf->buffer);
+	return (parser_arg) bencode_list(ctx->buffer);
 }
 
 static void bencode_pretty_print(bencode_item_t *el, GString *s);
@@ -174,11 +174,15 @@ static parser_arg __bencode_list_add(bencode_item_t *l, bencode_item_t *e) {
 static parser_arg __bencode_list_add_dictionary(bencode_item_t *l) {
 	return (parser_arg) bencode_list_add_dictionary(l);
 }
-static str *__bencode_collapse_str(ng_parser_ctx_t *ctx, bencode_item_t *a, str *out) {
-	return bencode_collapse_str(a, out);
+static str __bencode_collapse_str(ng_parser_ctx_t *ctx, bencode_item_t *a, void **to_free) {
+	return bencode_collapse_str(a);
 }
 static const char *__bencode_strdup(ng_parser_ctx_t *ctx, const char *s) {
-	return bencode_strdup(&ctx->ngbuf->buffer, s);
+	return bencode_strdup(ctx->buffer, s);
+}
+static void __bencode_ctx_init(ng_parser_ctx_t *ctx, bencode_buffer_t *buf) {
+	bencode_buffer_init(buf);
+	*ctx = (ng_parser_ctx_t) { .parser = &ng_parser_native, .buffer = buf };
 }
 
 static bool json_is_dict(JsonNode *n) {
@@ -422,19 +426,23 @@ static void json_list_add_str(JsonNode *n, const str *v) {
 static void json_list_add_string(JsonNode *n, const char *s) {
 	json_array_add_string_element(json_node_get_array(n), s);
 }
-static str *json_collapse(ng_parser_ctx_t *ctx, JsonNode *a, str *out) {
+static str json_collapse(ng_parser_ctx_t *ctx, JsonNode *a, void **to_free) {
 	JsonGenerator *g = json_generator_new();
 	json_generator_set_root(g, a);
 	size_t len;
 	char *s = json_generator_to_data(g, &len);
+	*to_free = s;
 	g_object_unref(g);
-	*out = STR_LEN(s, len);
-	ctx->ngbuf->collapsed = s;
+	str out = STR_LEN(s, len);
 	json_node_unref(a);
 	return out;
 }
+static void json_ctx_init(ng_parser_ctx_t *ctx, bencode_buffer_t *buf) {
+	*ctx = (ng_parser_ctx_t) { .parser = &ng_parser_json };
+}
 
 const ng_parser_t ng_parser_native = {
+	.init = __bencode_ctx_init,
 	.collapse = __bencode_collapse_str,
 	.dict_iter = bencode_dict_iter,
 	.is_list = bencode_is_list,
@@ -468,6 +476,7 @@ const ng_parser_t ng_parser_native = {
 	.pretty_print = bencode_pretty_print,
 };
 const ng_parser_t ng_parser_json = {
+	.init = json_ctx_init,
 	.collapse = json_collapse,
 	.dict_iter = json_dict_iter,
 	.is_list = json_is_list,
@@ -649,11 +658,10 @@ static void control_ng_process_payload(ng_ctx *hctx, str *reply, str *data, cons
 	struct control_ng_stats* cur = get_control_ng_stats(&sin->address);
 	enum ng_command command = -1;
 
-	ng_parser_ctx_t parser_ctx = {
-		.parser = &ng_parser_native,
-	};
+	ng_command_ctx_t command_ctx = {0};
+	const ng_parser_t *parser = &ng_parser_native;
 
-	parser_ctx.ngbuf = *ngbufp = ng_buffer_new(ref);
+	command_ctx.ngbuf = *ngbufp = ng_buffer_new(ref);
 
 	errstr = "Invalid data (no payload)";
 	if (data->len <= 0)
@@ -661,26 +669,24 @@ static void control_ng_process_payload(ng_ctx *hctx, str *reply, str *data, cons
 
 	/* Bencode dictionary */
 	if (data->s[0] == 'd') {
-		int ret = bencode_buffer_init(&parser_ctx.ngbuf->buffer);
-		assert(ret == 0);
-		(void) ret;
+		ng_parser_native.init(&command_ctx.parser_ctx, &command_ctx.ngbuf->buffer);
 
-		parser_ctx.req.benc = bencode_decode_expect_str(&parser_ctx.ngbuf->buffer, data, BENCODE_DICTIONARY);
+		command_ctx.req.benc = bencode_decode_expect_str(&command_ctx.ngbuf->buffer, data, BENCODE_DICTIONARY);
 		errstr = "Could not decode bencode dictionary";
-		if (!parser_ctx.req.benc)
+		if (!command_ctx.req.benc)
 			goto err_send;
 	}
 
 	/* JSON */
 	else if (data->s[0] == '{') {
-		parser_ctx.parser = &ng_parser_json;
-		parser_ctx.ngbuf->json = json_parser_new();
+		ng_parser_json.init(&command_ctx.parser_ctx, &command_ctx.ngbuf->buffer);
+		command_ctx.ngbuf->json = json_parser_new();
 		errstr = "Failed to parse JSON document";
-		if (!json_parser_load_from_data(parser_ctx.ngbuf->json, data->s, data->len, NULL))
+		if (!json_parser_load_from_data(command_ctx.ngbuf->json, data->s, data->len, NULL))
 			goto err_send;
-		parser_ctx.req.json = json_parser_get_root(parser_ctx.ngbuf->json);
+		command_ctx.req.json = json_parser_get_root(command_ctx.ngbuf->json);
 		errstr = "Could not decode bencode dictionary";
-		if (!parser_ctx.req.json || !parser_ctx.parser->is_dict(parser_ctx.req))
+		if (!command_ctx.req.json || !ng_parser_json.is_dict(command_ctx.req))
 			goto err_send;
 	}
 
@@ -689,15 +695,17 @@ static void control_ng_process_payload(ng_ctx *hctx, str *reply, str *data, cons
 		goto err_send;
 	}
 
-	parser_ctx.resp = parser_ctx.parser->dict(&parser_ctx);
-	assert(parser_ctx.resp.gen != NULL);
+	parser = command_ctx.parser_ctx.parser;
+
+	command_ctx.resp = parser->dict(&command_ctx.parser_ctx);
+	assert(command_ctx.resp.gen != NULL);
 
-	parser_ctx.parser->dict_get_str(parser_ctx.req, "command", &cmd);
+	parser->dict_get_str(command_ctx.req, "command", &cmd);
 	errstr = "Dictionary contains no key \"command\"";
 	if (!cmd.s)
 		goto err_send;
 
-	parser_ctx.parser->dict_get_str(parser_ctx.req, "call-id", &callid);
+	parser->dict_get_str(command_ctx.req, "call-id", &callid);
 	log_info_str(&callid);
 
 	ilogs(control, LOG_INFO, "Received command '"STR_FORMAT"' from %s", STR_FMT(&cmd), addr);
@@ -706,7 +714,7 @@ static void control_ng_process_payload(ng_ctx *hctx, str *reply, str *data, cons
 		log_str = g_string_sized_new(256);
 		g_string_append_printf(log_str, "Dump for '"STR_FORMAT"' from %s: %s", STR_FMT(&cmd), addr,
 				rtpe_config.common.log_mark_prefix);
-		parser_ctx.parser->pretty_print(parser_ctx.req, log_str);
+		parser->pretty_print(command_ctx.req, log_str);
 		g_string_append(log_str, rtpe_config.common.log_mark_suffix);
 		ilogs(control, LOG_DEBUG, "%.*s", (int) log_str->len, log_str->str);
 		g_string_free(log_str, TRUE);
@@ -724,99 +732,99 @@ static void control_ng_process_payload(ng_ctx *hctx, str *reply, str *data, cons
 			command = NGC_PING;
 			break;
 		case CSH_LOOKUP("offer"):
-			errstr = call_offer_ng(&parser_ctx, addr, sin);
+			errstr = call_offer_ng(&command_ctx, addr, sin);
 			command = NGC_OFFER;
 			break;
 		case CSH_LOOKUP("answer"):
-			errstr = call_answer_ng(&parser_ctx);
+			errstr = call_answer_ng(&command_ctx);
 			command = NGC_ANSWER;
 			break;
 		case CSH_LOOKUP("delete"):
-			errstr = call_delete_ng(&parser_ctx);
+			errstr = call_delete_ng(&command_ctx);
 			command = NGC_DELETE;
 			break;
 		case CSH_LOOKUP("query"):
-			errstr = call_query_ng(&parser_ctx);
+			errstr = call_query_ng(&command_ctx);
 			command = NGC_QUERY;
 			break;
 		case CSH_LOOKUP("list"):
-			errstr = call_list_ng(&parser_ctx);
+			errstr = call_list_ng(&command_ctx);
 			command = NGC_LIST;
 			break;
 		case CSH_LOOKUP("start recording"):
-			errstr = call_start_recording_ng(&parser_ctx);
+			errstr = call_start_recording_ng(&command_ctx);
 			command = NGC_START_RECORDING;
 			break;
 		case CSH_LOOKUP("stop recording"):
-			errstr = call_stop_recording_ng(&parser_ctx);
+			errstr = call_stop_recording_ng(&command_ctx);
 			command = NGC_STOP_RECORDING;
 			break;
 		case CSH_LOOKUP("pause recording"):
-			errstr = call_pause_recording_ng(&parser_ctx);
+			errstr = call_pause_recording_ng(&command_ctx);
 			command = NGC_PAUSE_RECORDING;
 			break;
 		case CSH_LOOKUP("start forwarding"):
-			errstr = call_start_forwarding_ng(&parser_ctx);
+			errstr = call_start_forwarding_ng(&command_ctx);
 			command = NGC_START_FORWARDING;
 			break;
 		case CSH_LOOKUP("stop forwarding"):
-			errstr = call_stop_forwarding_ng(&parser_ctx);
+			errstr = call_stop_forwarding_ng(&command_ctx);
 			command = NGC_STOP_FORWARDING;
 			break;
 		case CSH_LOOKUP("block DTMF"):
-			errstr = call_block_dtmf_ng(&parser_ctx);
+			errstr = call_block_dtmf_ng(&command_ctx);
 			command = NGC_BLOCK_DTMF;
 			break;
 		case CSH_LOOKUP("unblock DTMF"):
-			errstr = call_unblock_dtmf_ng(&parser_ctx);
+			errstr = call_unblock_dtmf_ng(&command_ctx);
 			command = NGC_UNBLOCK_DTMF;
 			break;
 		case CSH_LOOKUP("block media"):
-			errstr = call_block_media_ng(&parser_ctx);
+			errstr = call_block_media_ng(&command_ctx);
 			command = NGC_BLOCK_MEDIA;
 			break;
 		case CSH_LOOKUP("unblock media"):
-			errstr = call_unblock_media_ng(&parser_ctx);
+			errstr = call_unblock_media_ng(&command_ctx);
 			command = NGC_UNBLOCK_MEDIA;
 			break;
 		case CSH_LOOKUP("silence media"):
-			errstr = call_silence_media_ng(&parser_ctx);
+			errstr = call_silence_media_ng(&command_ctx);
 			command = NGC_SILENCE_MEDIA;
 			break;
 		case CSH_LOOKUP("unsilence media"):
-			errstr = call_unsilence_media_ng(&parser_ctx);
+			errstr = call_unsilence_media_ng(&command_ctx);
 			command = NGC_UNSILENCE_MEDIA;
 			break;
 		case CSH_LOOKUP("play media"):
-			errstr = call_play_media_ng(&parser_ctx);
+			errstr = call_play_media_ng(&command_ctx);
 			command = NGC_PLAY_MEDIA;
 			break;
 		case CSH_LOOKUP("stop media"):
-			errstr = call_stop_media_ng(&parser_ctx);
+			errstr = call_stop_media_ng(&command_ctx);
 			command = NGC_STOP_MEDIA;
 			break;
 		case CSH_LOOKUP("play DTMF"):
-			errstr = call_play_dtmf_ng(&parser_ctx);
+			errstr = call_play_dtmf_ng(&command_ctx);
 			command = NGC_PLAY_DTMF;
 			break;
 		case CSH_LOOKUP("statistics"):
-			errstr = statistics_ng(&parser_ctx);
+			errstr = statistics_ng(&command_ctx);
 			command = NGC_STATISTICS;
 			break;
 		case CSH_LOOKUP("publish"):
-			errstr = call_publish_ng(&parser_ctx, addr, sin);
+			errstr = call_publish_ng(&command_ctx, addr, sin);
 			command = NGC_PUBLISH;
 			break;
 		case CSH_LOOKUP("subscribe request"):
-			errstr = call_subscribe_request_ng(&parser_ctx);
+			errstr = call_subscribe_request_ng(&command_ctx);
 			command = NGC_SUBSCRIBE_REQ;
 			break;
 		case CSH_LOOKUP("subscribe answer"):
-			errstr = call_subscribe_answer_ng(&parser_ctx);
+			errstr = call_subscribe_answer_ng(&command_ctx);
 			command = NGC_SUBSCRIBE_ANS;
 			break;
 		case CSH_LOOKUP("unsubscribe"):
-			errstr = call_unsubscribe_ng(&parser_ctx);
+			errstr = call_unsubscribe_ng(&command_ctx);
 			command = NGC_UNSUBSCRIBE;
 			break;
 		default:
@@ -841,7 +849,7 @@ static void control_ng_process_payload(ng_ctx *hctx, str *reply, str *data, cons
 	if (errstr)
 		goto err_send;
 
-	parser_ctx.parser->dict_add_string(parser_ctx.resp, "result", resultstr);
+	parser->dict_add_string(command_ctx.resp, "result", resultstr);
 
 	// update interval statistics
 	RTPE_STATS_INC(ng_commands[command]);
@@ -854,38 +862,34 @@ err_send:
 	if (errstr < magic_load_limit_strings[0] || errstr > magic_load_limit_strings[__LOAD_LIMIT_MAX-1]) {
 		ilogs(control, LOG_WARNING, "Protocol error in packet from %s: %s [" STR_FORMAT_M "]",
 				addr, errstr, STR_FMT_M(data));
-		parser_ctx.parser->dict_add_string(parser_ctx.resp, "result", "error");
-		parser_ctx.parser->dict_add_string(parser_ctx.resp, "error-reason", errstr);
+		parser->dict_add_string(command_ctx.resp, "result", "error");
+		parser->dict_add_string(command_ctx.resp, "error-reason", errstr);
 		g_atomic_int_inc(&cur->errors);
 		cmd = STR_NULL;
 	}
 	else {
-		parser_ctx.parser->dict_add_string(parser_ctx.resp, "result", "load limit");
-		parser_ctx.parser->dict_add_string(parser_ctx.resp, "message", errstr);
+		parser->dict_add_string(command_ctx.resp, "result", "load limit");
+		parser->dict_add_string(command_ctx.resp, "message", errstr);
 	}
 
 send_resp:
-	parser_ctx.parser->collapse(&parser_ctx, parser_ctx.resp, reply);
-
 	if (cmd.s) {
 		ilogs(control, LOG_INFO, "Replying to '"STR_FORMAT"' from %s (elapsed time %llu.%06llu sec)", STR_FMT(&cmd), addr, (unsigned long long)cmd_process_time.tv_sec, (unsigned long long)cmd_process_time.tv_usec);
 
 		if (get_log_level(control) >= LOG_DEBUG) {
-			parser_ctx.req.benc = bencode_decode_expect_str(&parser_ctx.ngbuf->buffer,
-					reply, BENCODE_DICTIONARY);
-			if (parser_ctx.req.benc) {
-				log_str = g_string_sized_new(256);
-				g_string_append_printf(log_str, "Response dump for '"STR_FORMAT"' to %s: %s",
-						STR_FMT(&cmd), addr,
-						rtpe_config.common.log_mark_prefix);
-				parser_ctx.parser->pretty_print(parser_ctx.req, log_str);
-				g_string_append(log_str, rtpe_config.common.log_mark_suffix);
-				ilogs(control, LOG_DEBUG, "%.*s", (int) log_str->len, log_str->str);
-				g_string_free(log_str, TRUE);
-			}
+			log_str = g_string_sized_new(256);
+			g_string_append_printf(log_str, "Response dump for '"STR_FORMAT"' to %s: %s",
+					STR_FMT(&cmd), addr,
+					rtpe_config.common.log_mark_prefix);
+			parser->pretty_print(command_ctx.resp, log_str);
+			g_string_append(log_str, rtpe_config.common.log_mark_suffix);
+			ilogs(control, LOG_DEBUG, "%.*s", (int) log_str->len, log_str->str);
+			g_string_free(log_str, TRUE);
 		}
 	}
 
+	*reply = parser->collapse(&command_ctx.parser_ctx, command_ctx.resp, &command_ctx.ngbuf->collapsed);
+
 	release_closed_sockets();
 	log_info_pop_until(&callid);
 	CH(homer_trace_msg_out ,hctx, reply);
diff --git a/daemon/dtmf.c b/daemon/dtmf.c
index 40445ff79..40e3a8053 100644
--- a/daemon/dtmf.c
+++ b/daemon/dtmf.c
@@ -136,7 +136,7 @@ static void dtmf_bencode_and_notify(struct call_media *media, unsigned int event
 	bencode_dictionary_add_integer(data, "duration", ((long long) duration * (1000000LL / clockrate)) / 1000LL);
 	bencode_dictionary_add_integer(data, "volume", volume);
 
-	bencode_collapse_str(notify, &encoded_data);
+	encoded_data = bencode_collapse_str(notify);
 	notify_ng_tcp_clients(&encoded_data);
 	bencode_buffer_free(&bencbuf);
 }
diff --git a/daemon/redis.c b/daemon/redis.c
index e5cfeed78..b10186ff1 100644
--- a/daemon/redis.c
+++ b/daemon/redis.c
@@ -2405,7 +2405,7 @@ static void json_update_dtls_fingerprint(const ng_parser_t *parser, parser_arg i
  * encodes the few (k,v) pairs for one call under one json structure
  */
 
-static str redis_encode_json(ng_parser_ctx_t *ctx, call_t *c) {
+static str redis_encode_json(ng_parser_ctx_t *ctx, call_t *c, void **to_free) {
 
 	char tmp[2048];
 	const ng_parser_t *parser = ctx->parser;
@@ -2749,9 +2749,7 @@ static str redis_encode_json(ng_parser_ctx_t *ctx, call_t *c) {
 
 	}
 
-	str ret;
-	parser->collapse(ctx, root, &ret);
-	return ret;
+	return parser->collapse(ctx, root, to_free);
 }
 
 
@@ -2780,13 +2778,12 @@ void redis_update_onekey(call_t *c, struct redis *r) {
 		goto err;
 	}
 
-	ng_parser_ctx_t ctx = {.parser = redis_format_parsers[rtpe_config.redis_format]};
-	ctx.ngbuf = ng_buffer_new(NULL); // XXX make conditional
-	int ret = bencode_buffer_init(&ctx.ngbuf->buffer); // XXX make conditional and/or optimise
-	if (ret)
-		goto err;
+	ng_parser_ctx_t ctx;
+	bencode_buffer_t bbuf;
+	redis_format_parsers[rtpe_config.redis_format]->init(&ctx, &bbuf);
 
-	str result = redis_encode_json(&ctx, c);
+	void *to_free = NULL;
+	str result = redis_encode_json(&ctx, c, &to_free);
 	if (!result.len)
 		goto err;
 
@@ -2796,7 +2793,8 @@ void redis_update_onekey(call_t *c, struct redis *r) {
 
 	rwlock_unlock_r(&c->master_lock);
 
-	obj_put(ctx.ngbuf);
+	g_free(to_free);
+	bencode_buffer_free(ctx.buffer);
 
 	return;
 err:
diff --git a/daemon/statistics.c b/daemon/statistics.c
index be8454103..fc0a53558 100644
--- a/daemon/statistics.c
+++ b/daemon/statistics.c
@@ -952,11 +952,13 @@ void statistics_init(void) {
 	rtpe_codec_stats = codec_stats_ht_new();
 }
 
-const char *statistics_ng(ng_parser_ctx_t *ctx) {
+const char *statistics_ng(ng_command_ctx_t *ctx) {
 	g_autoptr(stats_metric_q) metrics = statistics_gather_metrics(NULL);
 	g_auto(GQueue) bstack = G_QUEUE_INIT;
 
 	parser_arg dict = ctx->resp;
+	ng_parser_ctx_t *parser_ctx = &ctx->parser_ctx;
+	const ng_parser_t *parser = parser_ctx->parser;
 	const char *sub_label = "statistics"; // top level
 
 	for (__auto_type l = metrics->head; l; l = l->next) {
@@ -967,13 +969,13 @@ const char *statistics_ng(ng_parser_ctx_t *ctx) {
 		// key:value entry?
 		if (m->value_short) {
 			if (m->is_int)
-				ctx->parser->dict_add_int(dict, ctx->parser->strdup(ctx, m->label),
+				parser->dict_add_int(dict, parser->strdup(parser_ctx, m->label),
 						m->int_value);
 			else if (m->value_raw)
-				ctx->parser->dict_add_str_dup(dict, ctx->parser->strdup(ctx, m->label),
+				parser->dict_add_str_dup(dict, parser->strdup(parser_ctx, m->label),
 						&STR(m->value_raw));
 			else
-				ctx->parser->dict_add_str_dup(dict, ctx->parser->strdup(ctx, m->label),
+				parser->dict_add_str_dup(dict, parser->strdup(parser_ctx, m->label),
 						&STR(m->value_short));
 			continue;
 		}
@@ -995,19 +997,19 @@ const char *statistics_ng(ng_parser_ctx_t *ctx) {
 		// open bracket of some sort - new sub-entry follows
 		parser_arg sub = {0};
 		if (m->is_brace)
-			sub = ctx->parser->dict(ctx);
+			sub = parser->dict(parser_ctx);
 		else
-			sub = ctx->parser->list(ctx);
+			sub = parser->list(parser_ctx);
 
 		assert(sub.gen != NULL);
 
 		// is this a dictionary?
-		if (ctx->parser->is_dict(dict)) {
+		if (parser->is_dict(dict)) {
 			assert(sub_label != NULL);
-			ctx->parser->dict_add(dict, ctx->parser->strdup(ctx, sub_label), sub);
+			parser->dict_add(dict, parser->strdup(parser_ctx, sub_label), sub);
 		}
-		else if (ctx->parser->is_list(dict))
-			ctx->parser->list_add(dict, sub);
+		else if (parser->is_list(dict))
+			parser->list_add(dict, sub);
 		else
 			abort();
 
diff --git a/include/bencode.h b/include/bencode.h
index 6b041a935..33c375c0e 100644
--- a/include/bencode.h
+++ b/include/bencode.h
@@ -216,8 +216,8 @@ struct iovec *bencode_iovec(bencode_item_t *root, int *cnt, unsigned int head, u
  * bencode_buffer_t object is destroyed. */
 char *bencode_collapse(bencode_item_t *root, size_t *len);
 
-/* Identical to bencode_collapse() but fills in a "str" object. Returns "out". */
-INLINE str *bencode_collapse_str(bencode_item_t *root, str *out);
+/* Identical to bencode_collapse() but returns a "str" object. */
+INLINE str bencode_collapse_str(bencode_item_t *root);
 
 /* Identical to bencode_collapse(), but the memory for the returned string is not allocated from
  * a bencode_buffer_t object, but instead using the function defined as BENCODE_MALLOC (normally
@@ -532,8 +532,9 @@ INLINE bencode_item_t *bencode_dictionary_get_expect(bencode_item_t *dict, const
 		return NULL;
 	return ret;
 }
-INLINE str *bencode_collapse_str(bencode_item_t *root, str *out) {
-	out->s = bencode_collapse(root, &out->len);
+INLINE str bencode_collapse_str(bencode_item_t *root) {
+	str out = STR_NULL;
+	out.s = bencode_collapse(root, &out.len);
 	return out;
 }
 INLINE int bencode_strcmp(bencode_item_t *a, const char *b) {
diff --git a/include/call.h b/include/call.h
index 22a0569a9..bcd885862 100644
--- a/include/call.h
+++ b/include/call.h
@@ -811,9 +811,9 @@ int monologue_subscribe_answer(struct call_monologue *dst, sdp_ng_flags *flags,
 int monologue_unsubscribe(struct call_monologue *dst, sdp_ng_flags *);
 void monologue_destroy(struct call_monologue *ml);
 int call_delete_branch_by_id(const str *callid, const str *branch,
-	const str *fromtag, const str *totag, ng_parser_ctx_t *, int delete_delay);
+	const str *fromtag, const str *totag, ng_command_ctx_t *, int delete_delay);
 int call_delete_branch(call_t *, const str *branch,
-	const str *fromtag, const str *totag, ng_parser_ctx_t *, int delete_delay);
+	const str *fromtag, const str *totag, ng_command_ctx_t *, int delete_delay);
 void call_destroy(call_t *);
 struct call_media *call_media_new(call_t *call);
 void call_media_free(struct call_media **mdp);
diff --git a/include/call_interfaces.h b/include/call_interfaces.h
index 66622790b..c491622d9 100644
--- a/include/call_interfaces.h
+++ b/include/call_interfaces.h
@@ -242,33 +242,33 @@ str *call_lookup_udp(char **);
 str *call_delete_udp(char **);
 str *call_query_udp(char **);
 
-const char *call_offer_ng(ng_parser_ctx_t *, const char*,
+const char *call_offer_ng(ng_command_ctx_t *, const char*,
 		const endpoint_t *);
-const char *call_answer_ng(ng_parser_ctx_t *);
-const char *call_delete_ng(ng_parser_ctx_t *);
-const char *call_query_ng(ng_parser_ctx_t *);
-const char *call_list_ng(ng_parser_ctx_t *);
-const char *call_start_recording_ng(ng_parser_ctx_t *);
-const char *call_stop_recording_ng(ng_parser_ctx_t *);
-const char *call_pause_recording_ng(ng_parser_ctx_t *);
-const char *call_start_forwarding_ng(ng_parser_ctx_t *);
-const char *call_stop_forwarding_ng(ng_parser_ctx_t *);
-const char *call_block_dtmf_ng(ng_parser_ctx_t *);
-const char *call_unblock_dtmf_ng(ng_parser_ctx_t *);
-const char *call_block_media_ng(ng_parser_ctx_t *);
-const char *call_unblock_media_ng(ng_parser_ctx_t *);
-const char *call_silence_media_ng(ng_parser_ctx_t *);
-const char *call_unsilence_media_ng(ng_parser_ctx_t *);
-const char *call_play_media_ng(ng_parser_ctx_t *);
-const char *call_stop_media_ng(ng_parser_ctx_t *);
-const char *call_play_dtmf_ng(ng_parser_ctx_t *);
-void ng_call_stats(ng_parser_ctx_t *, call_t *call, const str *fromtag, const str *totag,
+const char *call_answer_ng(ng_command_ctx_t *);
+const char *call_delete_ng(ng_command_ctx_t *);
+const char *call_query_ng(ng_command_ctx_t *);
+const char *call_list_ng(ng_command_ctx_t *);
+const char *call_start_recording_ng(ng_command_ctx_t *);
+const char *call_stop_recording_ng(ng_command_ctx_t *);
+const char *call_pause_recording_ng(ng_command_ctx_t *);
+const char *call_start_forwarding_ng(ng_command_ctx_t *);
+const char *call_stop_forwarding_ng(ng_command_ctx_t *);
+const char *call_block_dtmf_ng(ng_command_ctx_t *);
+const char *call_unblock_dtmf_ng(ng_command_ctx_t *);
+const char *call_block_media_ng(ng_command_ctx_t *);
+const char *call_unblock_media_ng(ng_command_ctx_t *);
+const char *call_silence_media_ng(ng_command_ctx_t *);
+const char *call_unsilence_media_ng(ng_command_ctx_t *);
+const char *call_play_media_ng(ng_command_ctx_t *);
+const char *call_stop_media_ng(ng_command_ctx_t *);
+const char *call_play_dtmf_ng(ng_command_ctx_t *);
+void ng_call_stats(ng_command_ctx_t *, call_t *call, const str *fromtag, const str *totag,
 		struct call_stats *totals);
-const char *call_publish_ng(ng_parser_ctx_t *, const char *,
+const char *call_publish_ng(ng_command_ctx_t *, const char *,
 		const endpoint_t *);
-const char *call_subscribe_request_ng(ng_parser_ctx_t *);
-const char *call_subscribe_answer_ng(ng_parser_ctx_t *);
-const char *call_unsubscribe_ng(ng_parser_ctx_t *);
+const char *call_subscribe_request_ng(ng_command_ctx_t *);
+const char *call_subscribe_answer_ng(ng_command_ctx_t *);
+const char *call_unsubscribe_ng(ng_command_ctx_t *);
 
 void add_media_to_sub_list(subscription_q *q, struct call_media *media, struct call_monologue *ml);
 
diff --git a/include/control_ng.h b/include/control_ng.h
index f37f7ac8e..579049cc6 100644
--- a/include/control_ng.h
+++ b/include/control_ng.h
@@ -65,7 +65,7 @@ struct ng_buffer {
 	struct sdp_chopper *chopper;
 	char *sdp_out;
 	struct call *call;
-	char *collapsed;
+	void *collapsed;
 };
 
 
@@ -118,7 +118,8 @@ typedef union {
 } helper_arg  __attribute__ ((__transparent_union__));
 
 struct ng_parser {
-	str *(*collapse)(ng_parser_ctx_t *, parser_arg, str *out);
+	void (*init)(ng_parser_ctx_t *, bencode_buffer_t *);
+	str (*collapse)(ng_parser_ctx_t *, parser_arg, void **);
 	bool (*dict_iter)(const ng_parser_t *, parser_arg,
 		void (*callback)(const ng_parser_t *, str *, parser_arg, helper_arg),
 		helper_arg);
@@ -157,6 +158,10 @@ struct ng_parser {
 };
 struct ng_parser_ctx {
 	const ng_parser_t *parser;
+	bencode_buffer_t *buffer;
+};
+struct ng_command_ctx {
+	ng_parser_ctx_t parser_ctx;
 	struct ng_buffer *ngbuf;
 	parser_arg req;
 	parser_arg resp;
diff --git a/include/statistics.h b/include/statistics.h
index c0a09521a..c221fd00e 100644
--- a/include/statistics.h
+++ b/include/statistics.h
@@ -173,7 +173,7 @@ void statistics_update_foreignown_inc(call_t * c);
 stats_metric_q *statistics_gather_metrics(struct interface_sampled_rate_stats *);
 void statistics_free_metrics(stats_metric_q *);
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(stats_metric_q, statistics_free_metrics)
-const char *statistics_ng(ng_parser_ctx_t *);
+const char *statistics_ng(ng_command_ctx_t *);
 enum thread_looper_action call_rate_stats_updater(void);
 
 /**
diff --git a/include/types.h b/include/types.h
index 4d92f77fd..c89c96b74 100644
--- a/include/types.h
+++ b/include/types.h
@@ -39,6 +39,7 @@ typedef void sdp_attr_print_f(GString *, union sdp_attr_print_arg, const sdp_ng_
 
 typedef struct ng_parser ng_parser_t;
 typedef struct ng_parser_ctx ng_parser_ctx_t;
+typedef struct ng_command_ctx ng_command_ctx_t;
 
 typedef struct bencode_item bencode_item_t;