Преглед на файлове

Merge pull request #786 from smititelu/master

rabbitmq: Add new module
Stefan Mititelu преди 9 години
родител
ревизия
7393cd0e76

+ 14 - 0
modules/rabbitmq/Makefile

@@ -0,0 +1,14 @@
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=rabbitmq.so
+LIBS=
+
+DEFS+=-DKAMAILIO_MOD_INTERFACE
+LIBS+=-lrabbitmq
+
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/kcore/kcore
+include ../../Makefile.modules

+ 244 - 0
modules/rabbitmq/README

@@ -0,0 +1,244 @@
+RABBITMQ Module
+
+Carsten Bock
+
+   ng-voice GmbH
+
+Edited by
+
+Stefan-Cristian Mititelu
+
+   <[email protected]>
+
+   Copyright © 2016 ONEm Communications Ltd.
+     __________________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1. Overview
+        2. Dependencies
+
+              2.1. Kamailio modules
+              2.2. External libraries or applications
+
+        3. Parameters
+
+              3.1. username (string)
+              3.2. password (string)
+              3.3. host (string)
+              3.4. vhost (string)
+              3.5. port (int)
+              3.6. timeout_sec (int)
+              3.7. timeout_usec (int)
+
+        4. Functions
+
+              4.1. rabbitmq_publish(exchange, routing_key, content_type,
+                      messagebody)
+
+              4.2. rabbitmq_publish_consume(exchange, routing_key,
+                      content_type, messagebody, reply)
+
+   List of Examples
+
+   1.1. Set the “username” parameter
+   1.2. Set the “password” parameter
+   1.3. Set the “host” parameter
+   1.4. Set the “vhost” parameter
+   1.5. Set the “port” parameter
+   1.6. Set the “timeout_sec” parameter
+   1.7. Set the “timeout_usec” parameter
+   1.8. rabbitmq_publish usage
+   1.9. rabbitmq_publish_consume usage
+
+Chapter 1. Admin Guide
+
+   Table of Contents
+
+   1. Overview
+   2. Dependencies
+
+        2.1. Kamailio modules
+        2.2. External libraries or applications
+
+   3. Parameters
+
+        3.1. username (string)
+        3.2. password (string)
+        3.3. host (string)
+        3.4. vhost (string)
+        3.5. port (int)
+        3.6. timeout_sec (int)
+        3.7. timeout_usec (int)
+
+   4. Functions
+
+        4.1. rabbitmq_publish(exchange, routing_key, content_type,
+                messagebody)
+
+        4.2. rabbitmq_publish_consume(exchange, routing_key, content_type,
+                messagebody, reply)
+
+1. Overview
+
+   This module offers amqp communication using librabbitmq. This module
+   was created based on rabbitmq-c C client. An new amqp connection is
+   setup on a per-children basis, when Kamailio starts. If connection is
+   lost, the process tries to re-establish it when a new amqp action is
+   required.
+
+   Currently librabbitmq offers no async API, but sync API, with a
+   timeout. See below link for updates on this issue:
+     * https://github.com/alanxz/rabbitmq-c/issues/370.
+
+2. Dependencies
+
+   2.1. Kamailio modules
+   2.2. External libraries or applications
+
+2.1. Kamailio modules
+
+   The following modules must be loaded before this module:
+     * none.
+
+2.2. External libraries or applications
+
+   The following libraries or applications must be installed before
+   running Kamailio with this module:
+     * librabbitmq-dev.
+
+3. Parameters
+
+   3.1. username (string)
+   3.2. password (string)
+   3.3. host (string)
+   3.4. vhost (string)
+   3.5. port (int)
+   3.6. timeout_sec (int)
+   3.7. timeout_usec (int)
+
+3.1. username (string)
+
+   The user name of amqp connection.
+
+   Default value is “"guest"”.
+
+   Example 1.1. Set the “username” parameter
+...
+modparam("rabbitmq", "username", "user")
+...
+
+3.2. password (string)
+
+   The password of amqp connection.
+
+   Default value is “"guest"”.
+
+   Example 1.2. Set the “password” parameter
+...
+modparam("rabbitmq", "password", "pass")
+...
+
+3.3. host (string)
+
+   The host of amqp connection.
+
+   Default value is “"localhost"”.
+
+   Example 1.3. Set the “host” parameter
+...
+modparam("rabbitmq", "host", "127.0.0.1")
+...
+
+3.4. vhost (string)
+
+   The vhost of the amqp connection.
+
+   Default value is “"/"”.
+
+   Example 1.4. Set the “vhost” parameter
+...
+modparam("rabbitmq", "vhost", "/vhost")
+...
+
+3.5. port (int)
+
+   The port of the amqp connection.
+
+   Default value is “5672”.
+
+   Example 1.5. Set the “port” parameter
+...
+modparam("rabbitmq", "port", 5672)
+...
+
+3.6. timeout_sec (int)
+
+   The timeout in seconds. The timeout_sec + timeout_usec combination
+   gives the time to wait for an amqp reply, when
+   rabbitmq_publish_consume() is used.
+
+   Default value is “1”.
+
+   Example 1.6. Set the “timeout_sec” parameter
+...
+modparam("rabbitmq", "timeout_sec", 1)
+...
+
+3.7. timeout_usec (int)
+
+   The timeout in micro seconds. The timeout_sec + timeout_usec
+   combination gives the time to wait for an amqp reply, when
+   rabbitmq_publish_consume() is used.
+
+   Default value is “0”.
+
+   Example 1.7. Set the “timeout_usec” parameter
+...
+modparam("rabbitmq", "timeout_usec", 0)
+...
+
+4. Functions
+
+   4.1. rabbitmq_publish(exchange, routing_key, content_type, messagebody)
+
+   4.2. rabbitmq_publish_consume(exchange, routing_key, content_type,
+          messagebody, reply)
+
+4.1.  rabbitmq_publish(exchange, routing_key, content_type, messagebody)
+
+   The function publishes messagebody without waiting for a reply.
+
+   Meaning of the parameters is as follows:
+     * exchange - the amqp exchange.
+     * routing_key - the amqp routing_key.
+     * content_type - the content_type of the messagebody.
+     * messagebody - the messagebody to be published.
+
+   This function can be used from REQUEST_ROUTE.
+
+   Example 1.8. rabbitmq_publish usage
+rabbitmq_publish("exchange", "routing_key", "application/json", "$avp(json_reque
+st)");
+
+4.2.  rabbitmq_publish_consume(exchange, routing_key, content_type,
+messagebody, reply)
+
+   The function publishes messagebody and waits timeoute_sec +
+   timeout_usec for a reply. If the reply comes, one can read it in the
+   reply avp.
+
+   Meaning of the parameters is as follows:
+     * exchange - the amqp exchange.
+     * routing_key - the amqp routing_key.
+     * content_type - the content_type of the messagebody.
+     * messagebody - the messagebody to be published.
+     * reply - the consumed reply.
+
+   This function can be used from REQUEST_ROUTE.
+
+   Example 1.9. rabbitmq_publish_consume usage
+rabbitmq_publish_consume("exchange", "routing_key", "application/json", "$avp(js
+on_request)", "$avp(json_reply)");

+ 4 - 0
modules/rabbitmq/doc/Makefile

@@ -0,0 +1,4 @@
+docs = rabbitmq.xml
+
+docbook_dir = ../../../docbook
+include $(docbook_dir)/Makefile.module

+ 39 - 0
modules/rabbitmq/doc/rabbitmq.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+    <bookinfo>
+	<title>RABBITMQ Module</title>
+	<productname class="trade">&kamailioname;</productname>
+	<authorgroup>
+            <author>
+                <firstname>Carsten</firstname>
+                <surname>Bock</surname>
+                <affiliation><orgname>ng-voice GmbH</orgname></affiliation>
+                <address>
+                <email>[email protected]</email>
+                </address>
+            </author>
+	    <editor>
+		<firstname>Stefan-Cristian</firstname>
+		<surname>Mititelu</surname>
+		<email>[email protected]</email>
+	    </editor>
+	</authorgroup>
+	<copyright>
+	    <year>2016</year>
+	    <holder>ONEm Communications Ltd.</holder>
+	</copyright>
+   </bookinfo>
+    <toc></toc>
+    
+    <xi:include href="rabbitmq_admin.xml"/>
+
+</book>

+ 338 - 0
modules/rabbitmq/doc/rabbitmq_admin.xml

@@ -0,0 +1,338 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module User's Guide -->
+
+<chapter xmlns:xi="http://www.w3.org/2001/XInclude">
+	
+	<title>&adminguide;</title>
+
+	<section>
+		<title>Overview</title>
+
+		<para>
+			This module offers amqp communication using librabbitmq.
+			This module was created based on <emphasis>rabbitmq-c</emphasis> C client.
+			An new amqp connection is setup on a per-children basis, when Kamailio starts.
+			If connection is lost, the process tries to re-establish it when a new amqp action is required.
+		</para>
+
+		<para>
+			Currently librabbitmq offers no async API, but sync API, with a timeout. See below link for updates on this issue:
+			<itemizedlist>
+				<listitem>
+					<para>
+						<emphasis>https://github.com/alanxz/rabbitmq-c/issues/370</emphasis>.
+					</para>
+				</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+
+	<section>
+		<title>Dependencies</title>
+
+		<section>
+			<title>&kamailio; modules</title>
+			<para>
+				The following modules must be loaded before this module:
+				<itemizedlist>
+					<listitem>
+						<para>
+							<emphasis>none</emphasis>.
+						</para>
+					</listitem>
+				</itemizedlist>
+			</para>
+		</section>
+
+		<section>
+			<title>External libraries or applications</title>
+			<para>
+				The following libraries or applications must be installed before
+				running &kamailio; with this module:
+				<itemizedlist>
+					<listitem>
+						<para>
+							<emphasis>librabbitmq-dev</emphasis>.
+						</para>
+					</listitem>
+				</itemizedlist>
+			</para>
+		</section>
+	</section>
+
+	<section>
+		<title>Parameters</title>
+
+		<section id="rabbitmq.p.username">
+			<title><varname>username</varname> (string)</title>
+			<para>
+				The user name of amqp connection.
+			</para>
+
+			<para>
+				<emphasis>
+					Default value is <quote>"guest"</quote>.
+				</emphasis>
+			</para>
+
+			<example>
+				<title>Set the <quote>username</quote> parameter</title>
+				<programlisting format="linespecific">
+...
+modparam("rabbitmq", "username", "user")
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="rabbitmq.p.password">
+			<title><varname>password</varname> (string)</title>
+			<para>
+				The password of amqp connection.
+			</para>
+
+			<para>
+				<emphasis>
+					Default value is <quote>"guest"</quote>.
+				</emphasis>
+			</para>
+
+			<example>
+				<title>Set the <quote>password</quote> parameter</title>
+				<programlisting format="linespecific">
+...
+modparam("rabbitmq", "password", "pass")
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="rabbitmq.p.host">
+			<title><varname>host</varname> (string)</title>
+			<para>
+				The host of amqp connection.
+			</para>
+
+			<para>
+				<emphasis>
+					Default value is <quote>"localhost"</quote>.
+				</emphasis>
+			</para>
+
+			<example>
+				<title>Set the <quote>host</quote> parameter</title>
+				<programlisting format="linespecific">
+...
+modparam("rabbitmq", "host", "127.0.0.1")
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="rabbitmq.p.vhost">
+			<title><varname>vhost</varname> (string)</title>
+			<para>
+				The vhost of the amqp connection.
+			</para>
+
+			<para>
+				<emphasis>
+					Default value is <quote>"/"</quote>.
+				</emphasis>
+			</para>
+
+			<example>
+				<title>Set the <quote>vhost</quote> parameter</title>
+				<programlisting format="linespecific">
+...
+modparam("rabbitmq", "vhost", "/vhost")
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="rabbitmq.p.port">
+			<title><varname>port</varname> (int)</title>
+			<para>
+				The port of the amqp connection.
+			</para>
+
+			<para>
+				<emphasis>
+					Default value is <quote>5672</quote>.
+				</emphasis>
+			</para>
+
+			<example>
+				<title>Set the <quote>port</quote> parameter</title>
+				<programlisting format="linespecific">
+...
+modparam("rabbitmq", "port", 5672)
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="rabbitmq.p.timeout_sec">
+			<title><varname>timeout_sec</varname> (int)</title>
+			<para>
+				The timeout in seconds.
+				The timeout_sec + timeout_usec combination gives the time to wait for an amqp reply, when rabbitmq_publish_consume() is used.
+			</para>
+
+			<para>
+				<emphasis>
+					Default value is <quote>1</quote>.
+				</emphasis>
+			</para>
+
+			<example>
+				<title>Set the <quote>timeout_sec</quote> parameter</title>
+				<programlisting format="linespecific">
+...
+modparam("rabbitmq", "timeout_sec", 1)
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="rabbitmq.p.timeout_usec">
+			<title><varname>timeout_usec</varname> (int)</title>
+			<para>
+				The timeout in micro seconds.
+				The timeout_sec + timeout_usec combination gives the time to wait for an amqp reply, when rabbitmq_publish_consume() is used.
+			</para>
+
+			<para>
+				<emphasis>
+					Default value is <quote>0</quote>.
+				</emphasis>
+			</para>
+
+			<example>
+				<title>Set the <quote>timeout_usec</quote> parameter</title>
+				<programlisting format="linespecific">
+...
+modparam("rabbitmq", "timeout_usec", 0)
+...
+				</programlisting>
+			</example>
+		</section>
+	</section>
+
+	<section>
+		<title>Functions</title>
+
+		<section id="rabbitmq.f.rabbitmq_publish">
+			<title>
+				<function moreinfo="none">rabbitmq_publish(exchange, routing_key, content_type, messagebody)</function>
+			</title>
+			<para>
+				The function publishes messagebody without waiting for a reply. 
+			</para>
+			<para>
+				Meaning of the parameters is as follows:
+			</para>
+				<itemizedlist>
+					<listitem>
+						<para>
+							<emphasis>exchange</emphasis> - the amqp exchange.
+						</para>
+					</listitem>
+
+					<listitem>
+						<para>
+							<emphasis>routing_key</emphasis> - the amqp routing_key.
+						</para>
+					</listitem>
+
+					<listitem>
+						<para>
+							<emphasis>content_type</emphasis> - the content_type of the messagebody.
+						</para>
+					</listitem>
+
+					<listitem>
+						<para>
+							<emphasis>messagebody</emphasis> - the messagebody to be published.
+						</para>
+					</listitem>
+				</itemizedlist>
+
+			<para>
+				This function can be used from REQUEST_ROUTE.
+			</para>
+
+			<example>
+				<title><function>rabbitmq_publish</function> usage</title>
+				<programlisting format="linespecific">
+rabbitmq_publish("exchange", "routing_key", "application/json", "$avp(json_request)");
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="rabbitmq.f.rabbitmq_publish_consume">
+			<title>
+				<function moreinfo="none">rabbitmq_publish_consume(exchange, routing_key, content_type, messagebody, reply)</function>
+			</title>
+			<para>
+				The function publishes messagebody and waits timeoute_sec + timeout_usec for a reply. 
+				If the reply comes, one can read it in the <emphasis>reply</emphasis> avp.
+			</para>
+			<para>
+				Meaning of the parameters is as follows:
+			</para>
+				<itemizedlist>
+					<listitem>
+						<para>
+							<emphasis>exchange</emphasis> - the amqp exchange.
+						</para>
+					</listitem>
+
+					<listitem>
+						<para>
+							<emphasis>routing_key</emphasis> - the amqp routing_key.
+						</para>
+					</listitem>
+
+					<listitem>
+						<para>
+							<emphasis>content_type</emphasis> - the content_type of the messagebody.
+						</para>
+					</listitem>
+
+					<listitem>
+						<para>
+							<emphasis>messagebody</emphasis> - the messagebody to be published.
+						</para>
+					</listitem>
+
+					<listitem>
+						<para>
+							<emphasis>reply</emphasis> - the consumed reply.
+						</para>
+					</listitem>
+				</itemizedlist>
+
+			<para>
+				This function can be used from REQUEST_ROUTE.
+			</para>
+
+			<example>
+				<title><function>rabbitmq_publish_consume</function> usage</title>
+				<programlisting format="linespecific">
+rabbitmq_publish_consume("exchange", "routing_key", "application/json", "$avp(json_request)", "$avp(json_reply)");
+				</programlisting>
+			</example>
+		</section>
+	</section>
+
+</chapter>

+ 579 - 0
modules/rabbitmq/rabbitmq.c

@@ -0,0 +1,579 @@
+#include "rabbitmq.h"
+#include "../../sr_module.h"
+#include "../../route_struct.h"
+#include "../../str.h"
+#include "../../mod_fix.h"
+#include "../../pvar.h"
+#include "../../lvalue.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <stdint.h>
+#include <amqp_tcp_socket.h>
+#include <amqp.h>
+#include <amqp_framing.h>
+
+#include <assert.h>
+
+#include "utils.h"
+
+MODULE_VERSION
+
+static int rabbitmq_publish(struct sip_msg*, char*, char*, char*, char*);
+static int rabbitmq_publish_consume(struct sip_msg*, char*, char*, char*, char*, char*);
+static int mod_init(void);
+static int mod_child_init(int);
+
+amqp_socket_t *amqp_sock = NULL;
+amqp_connection_state_t conn = NULL;
+
+/* module parameters */
+char *amqp_username = "guest";
+char *amqp_password = "guest";
+char *amqp_host = "localhost";
+char *amqp_vhost = "/";
+int amqp_port = 5672;
+int max_reconnect_attempts = 1;
+int timeout_sec = 1;
+int timeout_usec = 0;
+
+/* module helper functions */
+static int rabbitmq_connect(amqp_connection_state_t *conn);
+static int rabbitmq_disconnect(amqp_connection_state_t *conn);
+static int rabbitmq_reconnect(amqp_connection_state_t *conn);
+
+/* module fixup functions */
+static int fixup_params(void** param, int param_no)
+{
+	if (param_no == 5) {
+		if (fixup_pvar_null(param, 1) != 0) {
+			LM_ERR("failed to fixup result pvar\n");
+			return -1;
+		}
+		if (((pv_spec_t *)(*param))->setf == NULL) {
+			LM_ERR("result pvar is not writeble\n");
+			return -1;
+		}
+		return 0;
+	} else {
+		return fixup_spve_null(param, 1);
+	}
+
+	return -1;
+}
+
+static int fixup_free_params(void** param, int param_no)
+{
+	if (param_no == 5) {
+		return fixup_free_pvar_null(param, 1);
+	} else {
+		return fixup_free_spve_null(param, 1);
+	}
+
+	return -1;
+}
+
+/* module commands */
+static cmd_export_t cmds[] = {
+	{"rabbitmq_publish", (cmd_function)rabbitmq_publish, 4, fixup_params, fixup_free_params, REQUEST_ROUTE},
+	{"rabbitmq_publish_consume", (cmd_function)rabbitmq_publish_consume, 5, fixup_params, fixup_free_params, REQUEST_ROUTE},
+	{ 0, 0, 0, 0, 0, 0}
+};
+
+/* module parameters */
+static param_export_t params[] = {
+	{"username", PARAM_STRING, &amqp_username},
+	{"password", PARAM_STRING, &amqp_password},
+	{"host", PARAM_STRING, &amqp_host},
+	{"vhost", PARAM_STRING, &amqp_vhost},
+	{"port", PARAM_INT, &amqp_port},
+	{"timeout_sec", PARAM_INT, &timeout_sec},
+	{"timeout_usec", PARAM_INT, &timeout_usec},
+	{ 0, 0, 0}
+};
+
+/* module exports */
+struct module_exports exports = {
+	"rabbitmq", DEFAULT_DLFLAGS, /* dlopen flags */
+	cmds,			/* Exported functions */
+	params, 0,		   /* exported statistics */
+	0,			   /* exported MI functions */
+	0,			   /* exported pseudo-variables */
+	0,			   /* extra processes */
+	mod_init,		    /* module initialization function */
+	0,
+	0, mod_child_init	    /* per-child init function */
+};
+
+/* module init */
+static int mod_init(void)
+{
+	return 0;
+}
+
+
+/* module child init */
+static int mod_child_init(int rank) {
+	// main and tcp manager process
+	if (rank == PROC_MAIN || rank == PROC_TCP_MAIN) {
+		return 0;
+	}
+
+	// routing process
+	if (rabbitmq_connect(&conn) != RABBITMQ_OK) {
+		LM_ERR("FAIL rabbitmq_connect()");
+		return -1;
+	}
+	LM_DBG("SUCCESS initialization of rabbitmq module in child [%d]\n", rank);
+
+	return 0;
+}
+
+/* module helper functions */
+static int rabbitmq_publish(struct sip_msg* msg, char* in_exchange, char* in_routingkey, char* in_contenttype, char* in_messagebody) {
+	int reconnect_attempts = 0;
+	int log_ret;
+	str exchange, routingkey, messagebody, contenttype;
+	amqp_bytes_t reply_to_queue;
+
+	// sanity checks
+	if (get_str_fparam(&exchange, msg, (fparam_t*)in_exchange) < 0) {
+		LM_ERR("failed to get exchange\n");
+		return -1;
+	}
+
+	if (get_str_fparam(&routingkey, msg, (fparam_t*)in_routingkey) < 0) {
+		LM_ERR("failed to get kouting key\n");
+		return -1;
+	}
+
+	if (get_str_fparam(&messagebody, msg, (fparam_t*)in_messagebody) < 0) {
+		LM_ERR("failed to get message body\n");
+		return -1;
+	}
+
+	if (get_str_fparam(&contenttype, msg, (fparam_t*)in_contenttype) < 0) {
+		LM_ERR("failed to get content type\n");
+		return -1;
+	}
+
+reconnect:
+	// open channel
+	amqp_channel_open(conn, 1);
+	log_ret = log_on_amqp_error(amqp_get_rpc_reply(conn), "amqp_channel_open()");
+
+	// open channel - failed
+	if (log_ret != AMQP_RESPONSE_NORMAL) {
+		// reconnect - debug
+		LM_ERR("FAIL: rabbitmq_reconnect(), attempts=%d\n", reconnect_attempts);
+
+		// reconnect
+		if (reconnect_attempts < max_reconnect_attempts) {
+			// reconnect - debug
+			LM_ERR("RETRY: rabbitmq_reconnect()\n");
+
+			// reconnect - success
+			if (rabbitmq_reconnect(&conn) == RABBITMQ_OK) {
+				// reconnect - debug
+				LM_ERR("SUCCESS: rabbitmq_reconnect()\n");
+			}
+			reconnect_attempts++;
+
+			// reconnect - goto
+			goto reconnect;
+		}
+
+		// reconnect - close channel
+		amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS);
+
+		// reconnect - return error
+		return RABBITMQ_ERR_CHANNEL;
+	}
+
+	// alloc queue
+	amqp_queue_declare_ok_t *r = amqp_queue_declare(conn, 1, amqp_empty_bytes, 0, 0, 0, 1, amqp_empty_table);
+	if (log_on_amqp_error(amqp_get_rpc_reply(conn), "amqp_queue_declare()") != AMQP_RESPONSE_NORMAL) {
+		LM_ERR("FAIL: amqp_queue_declare()\n");
+		amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS);
+		return RABBITMQ_ERR_QUEUE;
+	}
+
+	// alloc bytes
+	reply_to_queue = amqp_bytes_malloc_dup(r->queue);
+	LM_DBG("%.*s\n", (int)reply_to_queue.len, (char*)reply_to_queue.bytes);
+	if (reply_to_queue.bytes == NULL) {
+		amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS);
+		amqp_bytes_free(reply_to_queue);
+		LM_ERR("Out of memory while copying queue name");
+		return -1;
+	}
+
+	// alloc properties
+	amqp_basic_properties_t props;
+	props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG |
+			AMQP_BASIC_DELIVERY_MODE_FLAG |
+			AMQP_BASIC_REPLY_TO_FLAG |
+			AMQP_BASIC_CORRELATION_ID_FLAG;
+	props.content_type = amqp_cstring_bytes(contenttype.s);
+	props.delivery_mode = 2; /* persistent delivery mode */
+	props.reply_to = amqp_bytes_malloc_dup(reply_to_queue);
+	if (props.reply_to.bytes == NULL) {
+		// debug
+		LM_ERR("Out of memory while copying queue name");
+
+		// cleanup
+		amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS);
+		amqp_bytes_free(reply_to_queue);
+
+		// error
+		return -1;
+	}
+	props.correlation_id = amqp_cstring_bytes("1");
+
+	// publish
+	if (log_on_error(amqp_basic_publish(conn,1,
+		amqp_cstring_bytes(exchange.s),
+		amqp_cstring_bytes(routingkey.s),
+		0,
+		0,
+		&props,
+		amqp_cstring_bytes(messagebody.s)),
+		"amqp_basic_publish()") != AMQP_RESPONSE_NORMAL) {
+			// debug
+			LM_ERR("FAIL: amqp_basic_publish()\n");
+
+			// cleanup
+			amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS);
+			amqp_bytes_free(reply_to_queue);
+
+			// error
+			return RABBITMQ_ERR_PUBLISH;
+	}
+
+	// debug
+	LM_DBG("SUCCESS: amqp_basic_publish()\n");
+
+	// cleanup
+	amqp_bytes_free(props.reply_to);
+	amqp_bytes_free(reply_to_queue);
+	amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS);
+
+	// success
+	return RABBITMQ_OK;
+}
+
+static int rabbitmq_publish_consume(struct sip_msg* msg, char* in_exchange, char* in_routingkey, char* in_contenttype, char* in_messagebody, char* reply) {
+	pv_spec_t *dst;
+	pv_value_t val;
+	str exchange, routingkey, messagebody, contenttype;
+	amqp_frame_t frame;
+	amqp_basic_deliver_t *d;
+	amqp_basic_properties_t *p;
+	int result = RABBITMQ_OK;
+	int reconnect_attempts = 0;
+	int log_ret;
+	size_t body_target;
+	size_t body_received;
+
+	struct timeval tv;
+	tv.tv_sec=timeout_sec;
+	tv.tv_usec=timeout_usec;
+
+	// sanity checks
+	if (get_str_fparam(&exchange, msg, (fparam_t*)in_exchange) < 0) {
+		LM_ERR("failed to get exchange\n");
+		return -1;
+	}
+
+	if (get_str_fparam(&routingkey, msg, (fparam_t*)in_routingkey) < 0) {
+		LM_ERR("failed to get kouting key\n");
+		return -1;
+	}
+
+	if (get_str_fparam(&messagebody, msg, (fparam_t*)in_messagebody) < 0) {
+		LM_ERR("failed to get message body\n");
+		return -1;
+	}
+
+	if (get_str_fparam(&contenttype, msg, (fparam_t*)in_contenttype) < 0) {
+		LM_ERR("failed to get content type\n");
+		return -1;
+	}
+
+
+	amqp_bytes_t reply_to_queue;
+
+reconnect:
+	// open channel
+	amqp_channel_open(conn, 1);
+	log_ret = log_on_amqp_error(amqp_get_rpc_reply(conn), "amqp_channel_open()");
+
+	// open channel - failed
+	if (log_ret != AMQP_RESPONSE_NORMAL) {
+		// reconnect - debug
+		LM_ERR("FAIL: rabbitmq_reconnect(), attempts=%d\n", reconnect_attempts);
+
+		// reconnect
+		if (reconnect_attempts < max_reconnect_attempts) {
+			// reconnect - debug
+			LM_ERR("RETRY: rabbitmq_reconnect()\n");
+
+			// reconnect - success
+			if (rabbitmq_reconnect(&conn) == RABBITMQ_OK) {
+				// reconnect - debug
+				LM_ERR("SUCCESS: rabbitmq_reconnect()\n");
+			}
+			reconnect_attempts++;
+
+			// reconnect - goto
+			goto reconnect;
+		}
+
+		// reconnect - close channel
+		amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS);
+
+		// reconnect - return error
+		return RABBITMQ_ERR_CHANNEL;
+	}
+
+	// alloc queue
+	amqp_queue_declare_ok_t *r = amqp_queue_declare(conn, 1, amqp_empty_bytes, 0, 0, 0, 1, amqp_empty_table);
+	if (log_on_amqp_error(amqp_get_rpc_reply(conn), "amqp_queue_declare()") != AMQP_RESPONSE_NORMAL) {
+		LM_ERR("FAIL: amqp_queue_declare()\n");
+		amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS);
+		return RABBITMQ_ERR_QUEUE;
+	}
+
+	// alloc bytes
+	reply_to_queue = amqp_bytes_malloc_dup(r->queue);
+	LM_DBG("%.*s\n", (int)reply_to_queue.len, (char*)reply_to_queue.bytes);
+	if (reply_to_queue.bytes == NULL) {
+		amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS);
+		amqp_bytes_free(reply_to_queue);
+		LM_ERR("Out of memory while copying queue name");
+		return -1;
+	}
+
+	// alloc properties
+	amqp_basic_properties_t props;
+	props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG |
+			AMQP_BASIC_DELIVERY_MODE_FLAG |
+			AMQP_BASIC_REPLY_TO_FLAG |
+			AMQP_BASIC_CORRELATION_ID_FLAG;
+	props.content_type = amqp_cstring_bytes(contenttype.s);
+	props.delivery_mode = 2; /* persistent delivery mode */
+	props.reply_to = amqp_bytes_malloc_dup(reply_to_queue);
+	if (props.reply_to.bytes == NULL) {
+		amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS);
+		amqp_bytes_free(reply_to_queue);
+		LM_ERR("Out of memory while copying queue name");
+		return -1;
+	}
+	props.correlation_id = amqp_cstring_bytes("1");
+
+	// publish
+	if (log_on_error(amqp_basic_publish(conn,1,
+		amqp_cstring_bytes(exchange.s),
+		amqp_cstring_bytes(routingkey.s),
+		0,
+		0,
+		&props,
+		amqp_cstring_bytes(messagebody.s)),
+		"amqp_basic_publish()") != AMQP_RESPONSE_NORMAL) {
+			LM_ERR("FAIL: amqp_basic_publish()\n");
+			amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS);
+			amqp_bytes_free(reply_to_queue);
+			return RABBITMQ_ERR_PUBLISH;
+	}
+	amqp_bytes_free(props.reply_to);
+
+	// consume
+	amqp_basic_consume(conn, 1, reply_to_queue, amqp_empty_bytes, 0, 1, 0, amqp_empty_table);
+	if (log_on_amqp_error(amqp_get_rpc_reply(conn), "amqp_basic_consume()") != AMQP_RESPONSE_NORMAL) {
+		LM_ERR("FAIL: amqp_basic_consume()\n");
+		amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS);
+		amqp_bytes_free(reply_to_queue);
+		return RABBITMQ_ERR_CONSUME;
+	}
+	amqp_bytes_free(reply_to_queue);
+
+	// consume frame
+	for (;;) {
+		amqp_maybe_release_buffers(conn);
+		result = amqp_simple_wait_frame_noblock(conn, &frame, &tv);
+		if (result < 0) {
+			LM_ERR("amqp_simple_wait_frame_noblock() error: %d\n", result);
+			result = -1;
+			break;
+		} else {
+			result = RABBITMQ_OK;
+		}
+
+		LM_DBG("Frame type: %u channel: %u\n", frame.frame_type, frame.channel);
+		if (frame.frame_type != AMQP_FRAME_METHOD) {
+			continue;
+		}
+
+		LM_DBG("Method: %s\n", amqp_method_name(frame.payload.method.id));
+		if (frame.payload.method.id != AMQP_BASIC_DELIVER_METHOD) {
+			continue;
+		}
+
+		d = (amqp_basic_deliver_t *) frame.payload.method.decoded;
+		LM_DBG("Delivery: %u exchange: %.*s routingkey: %.*s\n",
+				(unsigned) d->delivery_tag,
+				(int) d->exchange.len, (char *) d->exchange.bytes,
+				(int) d->routing_key.len, (char *) d->routing_key.bytes);
+
+		result = amqp_simple_wait_frame_noblock(conn, &frame, &tv);
+		if (result < 0) {
+			LM_ERR("amqp_simple_wait_frame_noblock() error: %d\n", result);
+			result = -1;
+			break;
+		} else {
+			result = RABBITMQ_OK;
+		}
+
+		if (frame.frame_type != AMQP_FRAME_HEADER) {
+			LM_ERR("Expected header!");
+			result = -1;
+			break;
+		}
+
+		p = (amqp_basic_properties_t *) frame.payload.properties.decoded;
+		if (p->_flags & AMQP_BASIC_CONTENT_TYPE_FLAG) {
+			LM_DBG("Content-type: %.*s\n",
+				(int) p->content_type.len, (char *) p->content_type.bytes);
+		}
+
+		body_target = (size_t)frame.payload.properties.body_size;
+		body_received = 0;
+
+		while (body_received < body_target) {
+			result = amqp_simple_wait_frame_noblock(conn, &frame, &tv);
+			if (result < 0) {
+				LM_ERR("amqp_simple_wait_frame_noblock() error: %d\n", result);
+				result = -1;
+				break;
+			} else {
+				result = RABBITMQ_OK;
+			}
+
+			if (frame.frame_type != AMQP_FRAME_BODY) {
+				LM_ERR("Expected body!");
+				result = -1;
+				break;
+			}
+
+			body_received += frame.payload.body_fragment.len;
+			assert(body_received <= body_target);
+
+			val.rs.s = (char*)frame.payload.body_fragment.bytes;
+			val.rs.len = (int)frame.payload.body_fragment.len;
+
+			LM_DBG("RPC Call result: %.*s\n", val.rs.len, val.rs.s);
+			val.flags = PV_VAL_STR;
+			dst = (pv_spec_t *)reply;
+			dst->setf(msg, &dst->pvp, (int)EQ_T, &val);
+		}
+
+		// amqp_simple_wait_frame <= 0
+		if (body_received != body_target) {
+			LM_ERR("body_received != body_target'\n");
+			result = -1;
+			break;
+		}
+
+		// received reply
+		break;
+	}
+
+	amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS);
+
+	return result;
+}
+
+static int rabbitmq_connect(amqp_connection_state_t *conn) {
+	int ret;
+	int log_ret;
+//	amqp_rpc_reply_t reply;
+
+	// establish a new connection to RabbitMQ server
+	*conn = amqp_new_connection();
+	log_ret = log_on_amqp_error(amqp_get_rpc_reply(*conn), "amqp_new_connection()");
+	if (log_ret != AMQP_RESPONSE_NORMAL && log_ret != AMQP_RESPONSE_NONE) {
+		return RABBITMQ_ERR_CONNECT;
+	}
+
+	amqp_sock = amqp_tcp_socket_new(*conn);
+	if (!amqp_sock) {
+		LM_ERR("FAIL: create TCP amqp_sock");
+		amqp_destroy_connection(*conn);
+		return RABBITMQ_ERR_SOCK;
+	}
+
+	ret = amqp_socket_open(amqp_sock, amqp_host, amqp_port);
+	if (ret != AMQP_STATUS_OK) {
+		LM_ERR("FAIL: open TCP sock, amqp_status=%d", ret);
+		// amqp_destroy_connection(*conn);
+		return RABBITMQ_ERR_SOCK;
+	}
+
+	log_ret = log_on_amqp_error(amqp_login(*conn, amqp_vhost, 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, amqp_username, amqp_password), "amqp_login()");
+	if (log_ret != AMQP_RESPONSE_NORMAL && log_ret != AMQP_RESPONSE_NONE) {
+		LM_ERR("FAIL: amqp_login()\n");
+		// amqp_destroy_connection(*conn);
+		return RABBITMQ_ERR_CONSUME;
+	}
+
+	return RABBITMQ_OK;
+}
+
+static int rabbitmq_disconnect(amqp_connection_state_t *conn) {
+	int log_ret;
+
+	// sanity checks
+	if (!*conn) {
+		return RABBITMQ_ERR_NULL;
+	}
+
+/*
+	log_ret = log_on_amqp_error(amqp_connection_close(*conn, AMQP_REPLY_SUCCESS), "amqp_connection_close()");
+	if (log_ret != AMQP_RESPONSE_NORMAL && log_ret != AMQP_RESPONSE_NONE) {
+		LM_ERR("FAIL: amqp_connection_close()\n");
+		return RABBITMQ_ERR_CONNECT;
+	}
+*/
+
+	log_ret = log_on_error(amqp_destroy_connection(*conn), "amqp_destroy_connection()");
+	if (log_ret != AMQP_RESPONSE_NORMAL && log_ret != AMQP_RESPONSE_NONE) {
+		LM_ERR("FAIL: amqp_destroy_connection()\n");
+		return RABBITMQ_ERR_CONNECT;
+	}
+
+	return RABBITMQ_OK;
+}
+static int rabbitmq_reconnect(amqp_connection_state_t *conn) {
+	int ret;
+
+	// sanity checks
+	if (!*conn) {
+		return RABBITMQ_ERR_NULL;
+	}
+
+	// disconnect from old RabbitMQ server
+	if ((ret = rabbitmq_disconnect(conn)) != RABBITMQ_OK) {
+		LM_NOTICE("FAIL rabbitmq_disconnect() in rabbitmq_reconnect()\n");
+		return ret;
+	}
+
+	// connect to new RabbitMQ server
+	if ((ret = rabbitmq_connect(conn)) != RABBITMQ_OK) {
+		LM_NOTICE("FAIL rabbitmq_connect() in rabbitmq_reconnect()\n");
+		return ret;
+	}
+
+	return RABBITMQ_OK;
+}

+ 15 - 0
modules/rabbitmq/rabbitmq.h

@@ -0,0 +1,15 @@
+#ifndef _RTPENGINE_H
+#define _RTPENGINE_H
+
+typedef enum {
+	RABBITMQ_OK=1,
+	RABBITMQ_ERR_CONNECT,
+	RABBITMQ_ERR_CHANNEL,
+	RABBITMQ_ERR_QUEUE,
+	RABBITMQ_ERR_PUBLISH,
+	RABBITMQ_ERR_SOCK,
+	RABBITMQ_ERR_CONSUME,
+	RABBITMQ_ERR_NULL,
+} RABBITMQ_ENUM;
+
+#endif

+ 192 - 0
modules/rabbitmq/utils.c

@@ -0,0 +1,192 @@
+/* vim:set ft=c ts=2 sw=2 sts=2 et cindent: */
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MIT
+ *
+ * Portions created by Alan Antonuk are Copyright (c) 2012-2013
+ * Alan Antonuk. All Rights Reserved.
+ *
+ * Portions created by VMware are Copyright (c) 2007-2012 VMware, Inc.
+ * All Rights Reserved.
+ *
+ * Portions created by Tony Garnock-Jones are Copyright (c) 2009-2010
+ * VMware, Inc. and Tony Garnock-Jones. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * ***** END LICENSE BLOCK *****
+ */
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <stdint.h>
+#include <amqp.h>
+#include <amqp_framing.h>
+
+#include "utils.h"
+
+int log_on_error(int x, char const *context)
+{
+	if (x < 0) {
+		LM_ERR("%s: %s\n", context, amqp_error_string2(x));
+		return x;
+	}
+
+	return AMQP_RESPONSE_NORMAL;
+}
+
+int log_on_amqp_error(amqp_rpc_reply_t x, char const *context)
+{
+	switch (x.reply_type) {
+		case AMQP_RESPONSE_NONE:
+			LM_NOTICE("%s: AMQP_RESPONSE_NONE\n", context);
+			return AMQP_RESPONSE_NONE;
+
+		case AMQP_RESPONSE_NORMAL:
+			LM_DBG("%s: AMQP_RESPONSE_NORMAL\n", context);
+			return AMQP_RESPONSE_NORMAL;
+
+		case AMQP_RESPONSE_LIBRARY_EXCEPTION:
+			LM_ERR("%s: AMQP_RESPONSE_LIBRARY_EXCEPTION: %s\n", context, amqp_error_string2(x.library_error));
+			return AMQP_RESPONSE_LIBRARY_EXCEPTION;
+
+		case AMQP_RESPONSE_SERVER_EXCEPTION:
+			switch (x.reply.id) {
+				case AMQP_CONNECTION_CLOSE_METHOD: {
+					amqp_connection_close_t *m = (amqp_connection_close_t *) x.reply.decoded;
+					LM_ERR("%s: AMQP_CONNECTION_CLOSE_METHOD: server connection error %uh, message: %.*s\n",
+						context, m->reply_code, (int) m->reply_text.len, (char *) m->reply_text.bytes);
+					break;
+				}
+
+				case AMQP_CHANNEL_CLOSE_METHOD: {
+					amqp_channel_close_t *m = (amqp_channel_close_t *) x.reply.decoded;
+					LM_ERR("%s: AMQP_CHANNEL_CLOSE_METHOD: server channel error %uh, message: %.*s\n",
+						context, m->reply_code, (int) m->reply_text.len, (char *) m->reply_text.bytes);
+					break;
+				}
+
+				default:
+					LM_ERR("%s: unknown server error, method id 0x%08X\n", context, x.reply.id);
+					break;
+			}
+
+			LM_ERR("%s: AMQP_RESPONSE_SERVER_EXCEPTION: %s\n", context, amqp_error_string2(x.library_error));
+			return AMQP_RESPONSE_SERVER_EXCEPTION;
+
+		default:
+			LM_ERR("%s: unknown reply type 0x%08X\n", context, x.reply_type);
+			return x.reply_type;
+	}
+
+	LM_ERR("%s: unknown reply type, method id 0x%08X - should not reach here!\n", context, x.reply_type);
+	return x.reply_type;
+}
+
+static void dump_row(long count, int numinrow, int *chs)
+{
+	int i;
+
+	printf("%08lX:", count - numinrow);
+
+	if (numinrow > 0) {
+		for (i = 0; i < numinrow; i++) {
+			if (i == 8) {
+				printf(" :");
+			}
+			printf(" %02X", chs[i]);
+		}
+		for (i = numinrow; i < 16; i++) {
+			if (i == 8) {
+				printf(" :");
+			}
+			printf("	 ");
+		}
+		printf("	");
+		for (i = 0; i < numinrow; i++) {
+			if (isprint(chs[i])) {
+				printf("%c", chs[i]);
+			} else {
+				printf(".");
+			}
+		}
+	}
+	printf("\n");
+}
+
+static int rows_eq(int *a, int *b)
+{
+	int i;
+
+	for (i=0; i<16; i++)
+		if (a[i] != b[i]) {
+			return 0;
+		}
+
+	return 1;
+}
+
+void amqp_dump(void const *buffer, size_t len)
+{
+	unsigned char *buf = (unsigned char *) buffer;
+	long count = 0;
+	int numinrow = 0;
+	int chs[16];
+	int oldchs[16] = {0};
+	int showed_dots = 0;
+	size_t i;
+
+	for (i = 0; i < len; i++) {
+		int ch = buf[i];
+
+		if (numinrow == 16) {
+			int j;
+
+			if (rows_eq(oldchs, chs)) {
+				if (!showed_dots) {
+					showed_dots = 1;
+					printf("					.. .. .. .. .. .. .. .. : .. .. .. .. .. .. .. ..\n");
+				}
+			} else {
+				showed_dots = 0;
+				dump_row(count, numinrow, chs);
+			}
+
+			for (j=0; j<16; j++) {
+				oldchs[j] = chs[j];
+			}
+
+			numinrow = 0;
+		}
+
+		count++;
+		chs[numinrow++] = ch;
+	}
+
+	dump_row(count, numinrow, chs);
+
+	if (numinrow != 0) {
+		printf("%08lX:\n", count);
+	}
+}

+ 50 - 0
modules/rabbitmq/utils.h

@@ -0,0 +1,50 @@
+/* vim:set ft=c ts=2 sw=2 sts=2 et cindent: */
+#ifndef librabbitmq_examples_utils_h
+#define librabbitmq_examples_utils_h
+
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MIT
+ *
+ * Portions created by Alan Antonuk are Copyright (c) 2012-2013
+ * Alan Antonuk. All Rights Reserved.
+ *
+ * Portions created by VMware are Copyright (c) 2007-2012 VMware, Inc.
+ * All Rights Reserved.
+ *
+ * Portions created by Tony Garnock-Jones are Copyright (c) 2009-2010
+ * VMware, Inc. and Tony Garnock-Jones. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * ***** END LICENSE BLOCK *****
+ */
+
+#include "../../dprint.h"
+
+extern int log_on_error(int x, char const *context);
+extern int log_on_amqp_error(amqp_rpc_reply_t x, char const *context);
+
+extern void amqp_dump(void const *buffer, size_t len);
+
+extern uint64_t now_microseconds(void);
+extern void microsleep(int usec);
+
+#endif