コード例 #1
0
ファイル: stasis_channels.c プロジェクト: jcollie/asterisk
static struct stasis_message *create_channel_blob_message(struct ast_channel_snapshot *snapshot,
		struct stasis_message_type *type,
		struct ast_json *blob)
{
	struct stasis_message *msg;
	struct ast_channel_blob *obj;

	obj = ao2_alloc(sizeof(*obj), channel_blob_dtor);
	if (!obj) {
		return NULL;
	}

	if (snapshot) {
		obj->snapshot = snapshot;
		ao2_ref(obj->snapshot, +1);
	}
	if (!blob) {
		blob = ast_json_null();
	}
	obj->blob = ast_json_ref(blob);

	msg = stasis_message_create(type, obj);
	ao2_cleanup(obj);
	return msg;
}
コード例 #2
0
struct stasis_message *ast_endpoint_blob_create(struct ast_endpoint *endpoint,
	struct stasis_message_type *type, struct ast_json *blob)
{
	RAII_VAR(struct ast_endpoint_blob *, obj, NULL, ao2_cleanup);
	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);

	if (!type) {
		return NULL;
	}
	if (!blob) {
		blob = ast_json_null();
	}

	if (!(obj = ao2_alloc(sizeof(*obj), endpoint_blob_dtor))) {
		return NULL;
	}

	if (endpoint) {
		if (!(obj->snapshot = ast_endpoint_snapshot_create(endpoint))) {
			return NULL;
		}
	}

	obj->blob = ast_json_ref(blob);

	if (!(msg = stasis_message_create(type, obj))) {
		return NULL;
	}

	ao2_ref(msg, +1);
	return msg;
}
コード例 #3
0
void ast_system_publish_registry(const char *channeltype, const char *username, const char *domain, const char *status, const char *cause)
{
    RAII_VAR(struct ast_json *, registry, NULL, ast_json_unref);
    RAII_VAR(struct ast_json_payload *, payload, NULL, ao2_cleanup);
    RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);

    if (!ast_system_registry_type()) {
        return;
    }

    registry = ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: s}",
                             "type", "registry",
                             "channeltype", channeltype,
                             "username", username,
                             "domain", domain,
                             "status", status,
                             "cause", S_OR(cause, ""));

    if (!(payload = ast_json_payload_create(registry))) {
        return;
    }

    if (!(message = stasis_message_create(ast_system_registry_type(), payload))) {
        return;
    }

    stasis_publish(ast_system_topic(), message);
}
コード例 #4
0
ファイル: stasis.c プロジェクト: GGGO/asterisk
/*! \brief Publish single channel user event (for app_userevent compatibility) */
void ast_multi_object_blob_single_channel_publish(struct ast_channel *chan,
	struct stasis_message_type *type, struct ast_json *blob)
{
	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
	RAII_VAR(struct ast_channel_snapshot *, channel_snapshot, NULL, ao2_cleanup);
	RAII_VAR(struct ast_multi_object_blob *, multi, NULL, ao2_cleanup);

	if (!type) {
		return;
	}

	multi = ast_multi_object_blob_create(blob);
	if (!multi) {
		return;
	}

	channel_snapshot = ast_channel_snapshot_create(chan);
	ao2_ref(channel_snapshot, +1);
	ast_multi_object_blob_add(multi, STASIS_UMOS_CHANNEL, channel_snapshot);

	message = stasis_message_create(type, multi);
	if (message) {
		/* app_userevent still publishes to channel */
		stasis_publish(ast_channel_topic(chan), message);
	}
}
コード例 #5
0
ファイル: stasis.c プロジェクト: GGGO/asterisk
static void send_subscription_unsubscribe(struct stasis_topic *topic,
	struct stasis_subscription *sub)
{
	struct stasis_subscription_change *change;
	struct stasis_message *msg;

	/* This assumes that we have already unsubscribed */
	ast_assert(!stasis_subscription_is_subscribed(sub));

	if (!stasis_subscription_change_type()) {
		return;
	}

	change = subscription_change_alloc(topic, sub->uniqueid, "Unsubscribe");
	if (!change) {
		return;
	}

	msg = stasis_message_create(stasis_subscription_change_type(), change);
	if (!msg) {
		ao2_cleanup(change);
		return;
	}

	stasis_publish(topic, msg);

	/* Now we have to dispatch to the subscription itself */
	dispatch_message(sub, msg, 0);

	ao2_cleanup(msg);
	ao2_cleanup(change);
}
コード例 #6
0
/*! \brief Callback for \ref ast_unreal_pvt_callbacks \ref optimization_finished_cb */
static void local_optimization_finished_cb(struct ast_unreal_pvt *base, int success, unsigned int id)
{
	RAII_VAR(struct ast_json *, json_object, ast_json_null(), ast_json_unref);
	RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup);
	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
	struct local_pvt *p = (struct local_pvt *)base;

	if (!ast_local_optimization_end_type()) {
		return;
	}

	json_object = ast_json_pack("{s: i, s: i}", "success", success, "id", id);

	if (!json_object) {
		return;
	}

	payload = local_channel_optimization_blob(p, json_object);
	if (!payload) {
		return;
	}

	msg = stasis_message_create(ast_local_optimization_end_type(), payload);
	if (!msg) {
		return;
	}

	stasis_publish(ast_channel_topic(p->base.owner), msg);
}
コード例 #7
0
ファイル: res_corosync.c プロジェクト: neonecosystem/asterisk
/*! \brief Publish a Corosync ping to \ref stasis */
static void publish_corosync_ping_to_stasis(struct ast_event *event)
{
	struct corosync_ping_payload *payload;
	struct stasis_message *message;

	ast_assert(ast_event_get_type(event) == AST_EVENT_PING);
	ast_assert(event != NULL);

	if (!corosync_ping_message_type()) {
		return;
	}

	payload = ao2_t_alloc(sizeof(*payload), corosync_ping_payload_dtor, "Create ping payload");
	if (!payload) {
		return;
	}
	payload->event = event;

	message = stasis_message_create(corosync_ping_message_type(), payload);
	if (!message) {
		ao2_t_ref(payload, -1, "Destroy payload on off nominal");
		return;
	}

	stasis_publish(corosync_topic(), message);

	ao2_t_ref(payload, -1, "Hand ref to stasis");
	ao2_t_ref(message, -1, "Hand ref to stasis");
}
コード例 #8
0
/*!
 * \internal
 * \brief Post the \ref ast_local_bridge_type \ref stasis message
 * \since 12.0.0
 *
 * \param p local_pvt to raise the local bridge message
 *
 * \return Nothing
 */
static void publish_local_bridge_message(struct local_pvt *p)
{
	RAII_VAR(struct ast_multi_channel_blob *, multi_blob, NULL, ao2_cleanup);
	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
	RAII_VAR(struct ast_channel_snapshot *, one_snapshot, NULL, ao2_cleanup);
	RAII_VAR(struct ast_channel_snapshot *, two_snapshot, NULL, ao2_cleanup);
	struct ast_channel *owner;
	struct ast_channel *chan;

	if (!ast_local_bridge_type()) {
		return;
	}

	ast_unreal_lock_all(&p->base, &chan, &owner);

	blob = ast_json_pack("{s: s, s: s, s: b}",
		"context", p->context,
		"exten", p->exten,
		"can_optimize", !ast_test_flag(&p->base, AST_UNREAL_NO_OPTIMIZATION));
	if (!blob) {
		goto end;
	}

	multi_blob = ast_multi_channel_blob_create(blob);
	if (!multi_blob) {
		goto end;
	}

	one_snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(owner));
	if (!one_snapshot) {
		goto end;
	}

	two_snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan));
	if (!two_snapshot) {
		goto end;
	}

	ast_multi_channel_blob_add_channel(multi_blob, "1", one_snapshot);
	ast_multi_channel_blob_add_channel(multi_blob, "2", two_snapshot);

	msg = stasis_message_create(ast_local_bridge_type(), multi_blob);
	if (!msg) {
		goto end;
	}

	stasis_publish(ast_channel_topic(owner), msg);

end:
	ast_channel_unlock(owner);
	ast_channel_unref(owner);

	ast_channel_unlock(chan);
	ast_channel_unref(chan);

	ao2_unlock(&p->base);
}
コード例 #9
0
ファイル: stasis_cache.c プロジェクト: aderbas/asterisk
struct stasis_message *stasis_cache_clear_create(struct stasis_message *id_message)
{
	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);

	msg = stasis_message_create(stasis_cache_clear_type(), id_message);
	if (!msg) {
		return NULL;
	}

	ao2_ref(msg, +1);
	return msg;
}
コード例 #10
0
ファイル: parking_manager.c プロジェクト: hardikk/asterisk
void publish_parked_call(struct parked_user *pu, enum ast_parked_call_event_type event_type)
{
	RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);

	payload = parked_call_payload_from_parked_user(pu, event_type);
	if (!payload) {
		return;
	}

	msg = stasis_message_create(ast_parked_call_type(), payload);
	if (!msg) {
		return;
	}

	stasis_publish(ast_parking_topic(), msg);
}
コード例 #11
0
ファイル: parking_manager.c プロジェクト: hardikk/asterisk
void publish_parked_call_failure(struct ast_channel *parkee)
{
	RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);

	payload = parked_call_payload_from_failure(parkee);
	if (!payload) {
		return;
	}

	msg = stasis_message_create(ast_parked_call_type(), payload);
	if (!msg) {
		return;
	}

	stasis_publish(ast_parking_topic(), msg);
}
コード例 #12
0
ファイル: res_corosync.c プロジェクト: neonecosystem/asterisk
/*! \brief Publish cluster discovery to \ref stasis */
static void publish_cluster_discovery_to_stasis_full(struct corosync_node *node, int joined)
{
	struct ast_json *json;
	struct ast_json_payload *payload;
	struct stasis_message *message;
	char eid[18];
	const char *addr;

	ast_eid_to_str(eid, sizeof(eid), &node->eid);
	addr = ast_sockaddr_stringify_addr(&node->addr);

	ast_log(AST_LOG_NOTICE, "Node %u (%s) at %s %s the cluster\n",
		node->id,
		eid,
		addr,
		joined ? "joined" : "left");

	json = ast_json_pack("{s: s, s: i, s: s, s: i}",
		"address", addr,
		"node_id", node->id,
		"eid", eid,
		"joined", joined);
	if (!json) {
		return;
	}

	payload = ast_json_payload_create(json);
	if (!payload) {
		ast_json_unref(json);
		return;
	}

	message = stasis_message_create(ast_cluster_discovery_type(), payload);
	if (!message) {
		ast_json_unref(json);
		ao2_ref(payload, -1);
		return;
	}

	stasis_publish(ast_system_topic(), message);
	ast_json_unref(json);
	ao2_ref(payload, -1);
	ao2_ref(message, -1);
}
コード例 #13
0
ファイル: pickup.c プロジェクト: huangjingpei/asterisk
static int send_call_pickup_stasis_message(struct ast_channel *picking_up, struct ast_channel_snapshot *chan, struct ast_channel_snapshot *target)
{
	RAII_VAR(struct ast_multi_channel_blob *, pickup_payload, NULL, ao2_cleanup);
	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);

	if (!(pickup_payload = ast_multi_channel_blob_create(ast_json_null()))) {
		return -1;
	}

	ast_multi_channel_blob_add_channel(pickup_payload, "channel", chan);
	ast_multi_channel_blob_add_channel(pickup_payload, "target", target);

	if (!(msg = stasis_message_create(ast_call_pickup_type(), pickup_payload))) {
		return -1;
	}

	stasis_publish(ast_channel_topic(picking_up), msg);
	return 0;
}
コード例 #14
0
ファイル: stasis_wait.c プロジェクト: hardikk/asterisk
static struct stasis_message *caching_guarantee_create(void)
{
	RAII_VAR(struct caching_guarantee *, guarantee, NULL, ao2_cleanup);
	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);

	if (!(guarantee = ao2_alloc(sizeof(*guarantee), caching_guarantee_dtor))) {
		return NULL;
	}

	ast_mutex_init(&guarantee->lock);
	ast_cond_init(&guarantee->cond, NULL);

	if (!(msg = stasis_message_create(cache_guarantee_type(), guarantee))) {
		return NULL;
	}

	ao2_ref(msg, +1);
	return msg;
}
コード例 #15
0
/*! \brief Callback for \ref ast_unreal_pvt_callbacks \ref optimization_started_cb */
static void local_optimization_started_cb(struct ast_unreal_pvt *base, struct ast_channel *source,
		enum ast_unreal_channel_indicator dest, unsigned int id)
{
	RAII_VAR(struct ast_json *, json_object, ast_json_null(), ast_json_unref);
	RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup);
	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
	struct local_pvt *p = (struct local_pvt *)base;

	if (!ast_local_optimization_begin_type()) {
		return;
	}

	json_object = ast_json_pack("{s: i, s: i}",
			"dest", dest, "id", id);

	if (!json_object) {
		return;
	}

	payload = local_channel_optimization_blob(p, json_object);
	if (!payload) {
		return;
	}

	if (source) {
		RAII_VAR(struct ast_channel_snapshot *, source_snapshot, NULL, ao2_cleanup);
		source_snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(source));
		if (!source_snapshot) {
			return;
		}

		ast_multi_channel_blob_add_channel(payload, "source", source_snapshot);
	}

	msg = stasis_message_create(ast_local_optimization_begin_type(), payload);
	if (!msg) {
		return;
	}

	stasis_publish(ast_channel_topic(p->base.owner), msg);
}
コード例 #16
0
ファイル: app_cdr.c プロジェクト: huangjingpei/asterisk
static int publish_app_cdr_message(struct ast_channel *chan, struct app_cdr_message_payload *payload)
{
	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
	RAII_VAR(struct stasis_message_router *, router, ast_cdr_message_router(), ao2_cleanup);

	if (!router) {
		ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: no message router\n",
			ast_channel_name(chan));
		return -1;
	}

	message = stasis_message_create(appcdr_message_type(), payload);
	if (!message) {
		ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: unable to create message\n",
			payload->channel_name);
		return -1;
	}
	stasis_message_router_publish_sync(router, message);

	return 0;
}
コード例 #17
0
ファイル: app_chanspy.c プロジェクト: aderbas/asterisk
/*! \internal
 * \brief Publish the chanspy message over Stasis-Core
 * \param spyer The channel doing the spying
 * \param spyee Who is being spied upon
 * \start start If non-zero, the spying is starting. Otherwise, the spyer is
 * finishing
 */
static void publish_chanspy_message(struct ast_channel *spyer,
									struct ast_channel *spyee,
									int start)
{
	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
	RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup);
	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);

	if (!spyer) {
		ast_log(AST_LOG_WARNING, "Attempt to publish ChanSpy message for NULL spyer channel\n");
		return;
	}
	blob = ast_json_null();
	if (!blob) {
		return;
	}

	payload = ast_multi_channel_blob_create(blob);
	if (!payload) {
		return;
	}

	if (pack_channel_into_message(spyer, "spyer_channel", payload)) {
		return;
	}

	if (spyee) {
		if (pack_channel_into_message(spyee, "spyee_channel", payload)) {
			return;
		}
	}

	message = stasis_message_create(
			start ? ast_channel_chanspy_start_type(): ast_channel_chanspy_stop_type(),
					payload);
	if (!message) {
		return;
	}
	stasis_publish(ast_channel_topic(spyer), message);
}
コード例 #18
0
ファイル: stasis_channels.c プロジェクト: jcollie/asterisk
void ast_channel_publish_snapshot(struct ast_channel *chan)
{
	RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);

	if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_SNAPSHOT_STAGE)) {
		return;
	}

	snapshot = ast_channel_snapshot_create(chan);
	if (!snapshot) {
		return;
	}

	message = stasis_message_create(ast_channel_snapshot_type(), snapshot);
	if (!message) {
		return;
	}

	ast_assert(ast_channel_topic(chan) != NULL);
	stasis_publish(ast_channel_topic(chan), message);
}
コード例 #19
0
ファイル: stasis_channels.c プロジェクト: jcollie/asterisk
void ast_publish_channel_state(struct ast_channel *chan)
{
	RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);

	ast_assert(chan != NULL);
	if (!chan) {
		return;
	}

	snapshot = ast_channel_snapshot_create(chan);
	if (!snapshot) {
		return;
	}

	message = stasis_message_create(ast_channel_snapshot_type(), snapshot);
	if (!message) {
		return;
	}

	ast_assert(ast_channel_topic(chan) != NULL);
	stasis_publish(ast_channel_topic(chan), message);
}
コード例 #20
0
ファイル: res_stasis_snoop.c プロジェクト: lyx2014/Asterisk
/*! \internal
 * \brief Publish the chanspy message over Stasis-Core
 * \param snoop The snoop structure
 * \start start If non-zero, the spying is starting. Otherwise, the spyer is
 * finishing
 */
static void publish_chanspy_message(struct stasis_app_snoop *snoop, int start)
{
	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
	RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup);
	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
	RAII_VAR(struct ast_channel_snapshot *, snoop_snapshot, NULL, ao2_cleanup);
	RAII_VAR(struct ast_channel_snapshot *, spyee_snapshot, NULL, ao2_cleanup);
	struct stasis_message_type *type = start ? ast_channel_chanspy_start_type(): ast_channel_chanspy_stop_type();

	blob = ast_json_null();
	if (!blob || !type) {
		return;
	}

	payload = ast_multi_channel_blob_create(blob);
	if (!payload) {
		return;
	}

	snoop_snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(snoop->chan));
	if (!snoop_snapshot) {
		return;
	}
	ast_multi_channel_blob_add_channel(payload, "spyer_channel", snoop_snapshot);

	spyee_snapshot = ast_channel_snapshot_get_latest(snoop->uniqueid);
	if (spyee_snapshot) {
		ast_multi_channel_blob_add_channel(payload, "spyee_channel", spyee_snapshot);
	}

	message = stasis_message_create(type, payload);
	if (!message) {
		return;
	}

	stasis_publish(ast_channel_topic(snoop->chan), message);
}
コード例 #21
0
ファイル: stasis_cache.c プロジェクト: aderbas/asterisk
static struct stasis_message *update_create(struct stasis_message *old_snapshot, struct stasis_message *new_snapshot)
{
	RAII_VAR(struct stasis_cache_update *, update, NULL, ao2_cleanup);
	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);

	ast_assert(old_snapshot != NULL || new_snapshot != NULL);

	update = ao2_alloc_options(sizeof(*update), stasis_cache_update_dtor,
		AO2_ALLOC_OPT_LOCK_NOLOCK);
	if (!update) {
		return NULL;
	}

	if (old_snapshot) {
		ao2_ref(old_snapshot, +1);
		update->old_snapshot = old_snapshot;
		if (!new_snapshot) {
			ao2_ref(stasis_message_type(old_snapshot), +1);
			update->type = stasis_message_type(old_snapshot);
		}
	}
	if (new_snapshot) {
		ao2_ref(new_snapshot, +1);
		update->new_snapshot = new_snapshot;
		ao2_ref(stasis_message_type(new_snapshot), +1);
		update->type = stasis_message_type(new_snapshot);
	}

	msg = stasis_message_create(stasis_cache_update_type(), update);
	if (!msg) {
		return NULL;
	}

	ao2_ref(msg, +1);
	return msg;
}
コード例 #22
0
ファイル: stasis_channels.c プロジェクト: jcollie/asterisk
void ast_channel_publish_dial_forward(struct ast_channel *caller, struct ast_channel *peer,
	struct ast_channel *forwarded, const char *dialstring, const char *dialstatus,
	const char *forward)
{
	RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup);
	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
	RAII_VAR(struct ast_channel_snapshot *, caller_snapshot, NULL, ao2_cleanup);
	RAII_VAR(struct ast_channel_snapshot *, peer_snapshot, NULL, ao2_cleanup);
	RAII_VAR(struct ast_channel_snapshot *, forwarded_snapshot, NULL, ao2_cleanup);

	ast_assert(peer != NULL);
	blob = ast_json_pack("{s: s, s: s, s: s}",
			     "dialstatus", S_OR(dialstatus, ""),
			     "forward", S_OR(forward, ""),
			     "dialstring", S_OR(dialstring, ""));
	if (!blob) {
		return;
	}
	payload = ast_multi_channel_blob_create(blob);
	if (!payload) {
		return;
	}

	if (caller) {
		ast_channel_lock(caller);
		caller_snapshot = ast_channel_snapshot_create(caller);
		ast_channel_unlock(caller);
		if (!caller_snapshot) {
			return;
		}
		ast_multi_channel_blob_add_channel(payload, "caller", caller_snapshot);
	}

	ast_channel_lock(peer);
	peer_snapshot = ast_channel_snapshot_create(peer);
	ast_channel_unlock(peer);
	if (!peer_snapshot) {
		return;
	}
	ast_multi_channel_blob_add_channel(payload, "peer", peer_snapshot);

	if (forwarded) {
		ast_channel_lock(forwarded);
		forwarded_snapshot = ast_channel_snapshot_create(forwarded);
		ast_channel_unlock(forwarded);
		if (!forwarded_snapshot) {
			return;
		}
		ast_multi_channel_blob_add_channel(payload, "forwarded", forwarded_snapshot);
	}

	msg = stasis_message_create(ast_channel_dial_type(), payload);
	if (!msg) {
		return;
	}

	if (forwarded) {
		struct stasis_subscription *subscription = stasis_subscribe(ast_channel_topic(peer), dummy_event_cb, NULL);

		stasis_publish(ast_channel_topic(peer), msg);
		stasis_unsubscribe_and_join(subscription);
	} else {
		publish_message_for_channel_topics(msg, caller);
	}
}
コード例 #23
0
ファイル: res_stasis_test.c プロジェクト: GGGO/asterisk
static struct timespec make_deadline(int timeout_millis)
{
	struct timeval start = ast_tvnow();
	struct timeval delta = {
		.tv_sec = timeout_millis / 1000,
		.tv_usec = (timeout_millis % 1000) * 1000,
	};
	struct timeval deadline_tv = ast_tvadd(start, delta);
	struct timespec deadline = {
		.tv_sec = deadline_tv.tv_sec,
		.tv_nsec = 1000 * deadline_tv.tv_usec,
	};

	return deadline;
}

struct stasis_message_sink *stasis_message_sink_create(void)
{
	RAII_VAR(struct stasis_message_sink *, sink, NULL, ao2_cleanup);

	sink = ao2_alloc(sizeof(*sink), stasis_message_sink_dtor);
	if (!sink) {
		return NULL;
	}
	ast_mutex_init(&sink->lock);
	ast_cond_init(&sink->cond, NULL);
	sink->max_messages = 4;
	sink->messages =
		ast_malloc(sizeof(*sink->messages) * sink->max_messages);
	if (!sink->messages) {
		return NULL;
	}
	ao2_ref(sink, +1);
	return sink;
}

/*!
 * \brief Implementation of the stasis_message_sink_cb() callback.
 *
 * Why the roundabout way of exposing this via stasis_message_sink_cb()? Well,
 * it has to do with how we load modules.
 *
 * Modules have their own metadata compiled into them in the module info block
 * at the end of the file.  This includes dependency information in the
 * \c nonoptreq field.
 *
 * Asterisk loads the module, inspects the field, then loads any needed
 * dependencies. This works because Asterisk passes \c RTLD_LAZY to the initial
 * dlopen(), which defers binding function references until they are called.
 *
 * But when you take the address of a function, that function needs to be
 * available at load time. So if some module used the address of
 * message_sink_cb() directly, and \c res_stasis_test.so wasn't loaded yet, then
 * that module would fail to load.
 *
 * The stasis_message_sink_cb() function gives us a layer of indirection so that
 * the initial lazy binding will still work as expected.
 */
static void message_sink_cb(void *data, struct stasis_subscription *sub,
	struct stasis_message *message)
{
	struct stasis_message_sink *sink = data;

	SCOPED_MUTEX(lock, &sink->lock);

	if (stasis_subscription_final_message(sub, message)) {
		sink->is_done = 1;
		ast_cond_signal(&sink->cond);
		return;
	}

	if (stasis_subscription_change_type() == stasis_message_type(message)) {
		/* Ignore subscription changes */
		return;
	}

	if (sink->num_messages == sink->max_messages) {
		size_t new_max_messages = sink->max_messages * 2;
		struct stasis_message **new_messages = ast_realloc(
			sink->messages,
			sizeof(*new_messages) * new_max_messages);
		if (!new_messages) {
			return;
		}
		sink->max_messages = new_max_messages;
		sink->messages = new_messages;
	}

	ao2_ref(message, +1);
	sink->messages[sink->num_messages++] = message;
	ast_cond_signal(&sink->cond);
}

stasis_subscription_cb stasis_message_sink_cb(void)
{
	return message_sink_cb;
}


int stasis_message_sink_wait_for_count(struct stasis_message_sink *sink,
	int num_messages, int timeout_millis)
{
	struct timespec deadline = make_deadline(timeout_millis);

	SCOPED_MUTEX(lock, &sink->lock);
	while (sink->num_messages < num_messages) {
		int r = ast_cond_timedwait(&sink->cond, &sink->lock, &deadline);

		if (r == ETIMEDOUT) {
			break;
		}
		if (r != 0) {
			ast_log(LOG_ERROR, "Unexpected condition error: %s\n",
				strerror(r));
			break;
		}
	}
	return sink->num_messages;
}

int stasis_message_sink_should_stay(struct stasis_message_sink *sink,
	int num_messages, int timeout_millis)
{
	struct timespec deadline = make_deadline(timeout_millis);

	SCOPED_MUTEX(lock, &sink->lock);
	while (sink->num_messages == num_messages) {
		int r = ast_cond_timedwait(&sink->cond, &sink->lock, &deadline);

		if (r == ETIMEDOUT) {
			break;
		}
		if (r != 0) {
			ast_log(LOG_ERROR, "Unexpected condition error: %s\n",
				strerror(r));
			break;
		}
	}
	return sink->num_messages;
}

int stasis_message_sink_wait_for(struct stasis_message_sink *sink, int start,
	stasis_wait_cb cmp_cb, const void *data, int timeout_millis)
{
	struct timespec deadline = make_deadline(timeout_millis);

	SCOPED_MUTEX(lock, &sink->lock);

	/* wait for the start */
	while (sink->num_messages < start + 1) {
		int r = ast_cond_timedwait(&sink->cond, &sink->lock, &deadline);

		if (r == ETIMEDOUT) {
			/* Timed out waiting for the start */
			return -1;
		}
		if (r != 0) {
			ast_log(LOG_ERROR, "Unexpected condition error: %s\n",
				strerror(r));
			return -2;
		}
	}


	while (!cmp_cb(sink->messages[start], data)) {
		++start;

		while (sink->num_messages < start + 1) {
			int r = ast_cond_timedwait(&sink->cond,
				&sink->lock, &deadline);

			if (r == ETIMEDOUT) {
				return -1;
			}
			if (r != 0) {
				ast_log(LOG_ERROR,
					"Unexpected condition error: %s\n",
					strerror(r));
				return -2;
			}
		}
	}

	return start;
}

struct stasis_message *stasis_test_message_create(void)
{
	RAII_VAR(void *, data, NULL, ao2_cleanup);

	if (!stasis_test_message_type()) {
		return NULL;
	}

	/* We just need the unique pointer; don't care what's in it */
	data = ao2_alloc(1, NULL);
	if (!data) {
		return NULL;
	}

	return stasis_message_create(stasis_test_message_type(), data);
}