static int cand_add(struct flow *flow, struct json_object *jcand)
{
	const char *mid = jzon_str(jcand, "sdp_mid");
	const char *rcand = jzon_str(jcand, "sdp");
	int idx;
	int err;

	err = jzon_int(&idx, jcand, "sdp_mline_index");
	if (err)
		return err;

	debug("cand_add: mid=%s idx=%d sdp=%s\n", mid, idx, rcand);

	if (flow->got_sdp) {
		struct mediaflow *mf = userflow_mediaflow(flow->userflow);
		return mediaflow_add_rcand(mf, rcand, mid, idx);
	}
	else {
		struct cand *cand = mem_zalloc(sizeof(*cand), cand_destructor);
		if (!cand)
			return ENOMEM;

		str_ncpy(cand->sdp, rcand, sizeof(cand->sdp));
		str_ncpy(cand->mid, mid, sizeof(cand->mid));
		cand->idx = idx;

		list_append(&flow->pendingl, &cand->le, cand);
		return 0;
	}
}
int zapi_connection_decode(struct json_object *jobj,
			   struct zapi_connection *conn)
{
	if (!jobj || !conn)
		return EINVAL;

	conn->status = jzon_str(jobj, "status");
	conn->conversation = jzon_str(jobj, "conversation");
	conn->to = jzon_str(jobj, "to");
	conn->from = jzon_str(jobj, "from");
	conn->last_update = jzon_str(jobj, "last_update");
	conn->message = jzon_str(jobj, "message");

	return 0;
}
int flow_sdp_handler(struct flow *flow, struct json_object *jobj, bool replayed)
{
	struct mediaflow *mf;
	const char *sdp;
	const char *state;
	bool strm_chg = false;
	bool isoffer;
	int err = 0;

	if (!flow || !jobj) {
		return EINVAL;
	}

	sdp   = jzon_str(jobj, "sdp");
	state = jzon_str(jobj, "state");
	isoffer = streq(state, "offer");

	
	mf = userflow_mediaflow(flow->userflow);
	debug("flow_sdp_handler(%p): state=%s uf=%p mf=%p\n",
	      flow, state, flow->userflow, mf);

	if (mf && mediaflow_sdp_is_complete(mf)) {
		if (replayed)
			return 0;

		strm_chg = strstr(sdp, "x-streamchange") != NULL;

		if (strm_chg) {
			info("flow_sdp_handler: x-streamchange\n");
			mediaflow_stop_media(mf);
			mediaflow_sdpstate_reset(mf);
			mediaflow_reset_media(mf);
		}
		else if (isoffer) {
			info("flow_sdp_handler: SDP re-offer detected.\n");
			flow_restart(flow);
		}
		else {
			warning("flow_sdp_handler: SDP already complete");
			return 0;
		}
	}

	if (isoffer) {
		err = userflow_accept(flow->userflow, sdp);
		if (strm_chg) {
			mediaflow_start_media(mf);
		}
	}
	else if (streq(state, "answer")) {
		err = userflow_update(flow->userflow, sdp);
	}
	else {
		warning("flowmgr: flow_sdp_handler: unknown state: %s\n",
			state);
		err = EINVAL;
	}

	flow->got_sdp = true;

	/* flush list of any pending ICE-candidates */
	if (!list_isempty(&flow->pendingl)) {

		struct le *le = list_head(&flow->pendingl);

		info("flowmgr: got SDP - adding %u pending candidates\n",
			  list_count(&flow->pendingl));

		while (le) {
			struct cand *cand = le->data;
			le = le->next;

			err |= mediaflow_add_rcand(
					   userflow_mediaflow(flow->userflow),
					   cand->sdp,
					   cand->mid, cand->idx);
			mem_deref(cand);
		}
	}

	return err;
}