summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexandru Scvortov <alexandru@rabbitmq.com>2011-09-21 17:53:11 +0100
committerAlexandru Scvortov <alexandru@rabbitmq.com>2011-09-21 17:53:11 +0100
commit5b925d512ad866125710ea505d08de33d5cc44a4 (patch)
treef48d74ce12897b2274ad8db95b3e3c1417624389
parentfe142c3131dcd63afc98c622973f4bde65c0adea (diff)
parentf743d965d235a882fa5c952aef30086c61ed94e9 (diff)
downloadrabbitmq-c-github-ask-5b925d512ad866125710ea505d08de33d5cc44a4.tar.gz
merge bug24440 into default (add amqp URL support to C client)
-rw-r--r--.hgignore1
-rw-r--r--librabbitmq/Makefile.am2
-rw-r--r--librabbitmq/amqp.h13
-rw-r--r--librabbitmq/amqp_api.c1
-rw-r--r--librabbitmq/amqp_private.h3
-rw-r--r--librabbitmq/amqp_url.c205
-rw-r--r--tests/Makefile.am4
-rw-r--r--tests/test_parse_url.c174
-rw-r--r--tests/test_tables.c376
-rw-r--r--tools/common.c147
10 files changed, 750 insertions, 176 deletions
diff --git a/.hgignore b/.hgignore
index 6018037..99d8fa6 100644
--- a/.hgignore
+++ b/.hgignore
@@ -20,6 +20,7 @@
^(|librabbitmq/|tests/|examples/|tools/|tools/doc/)Makefile(\.in)?$
^tests/test_tables$
+^tests/test_parse_url$
^examples/amqp_sendstring$
^examples/amqp_exchange_declare$
^examples/amqp_listen$
diff --git a/librabbitmq/Makefile.am b/librabbitmq/Makefile.am
index 1217c49..f5908c3 100644
--- a/librabbitmq/Makefile.am
+++ b/librabbitmq/Makefile.am
@@ -13,7 +13,7 @@ if USE_MSINTTYPES
AM_CFLAGS += -I$(top_srcdir)/msinttypes
endif
-librabbitmq_la_SOURCES = amqp_mem.c amqp_table.c amqp_connection.c amqp_socket.c amqp_api.c $(PLATFORM_DIR)/socket.c
+librabbitmq_la_SOURCES = amqp_mem.c amqp_table.c amqp_connection.c amqp_socket.c amqp_api.c amqp_url.c $(PLATFORM_DIR)/socket.c
librabbitmq_la_LDFLAGS = -no-undefined
librabbitmq_la_LIBADD = $(EXTRA_LIBS)
nodist_librabbitmq_la_SOURCES = amqp_framing.c
diff --git a/librabbitmq/amqp.h b/librabbitmq/amqp.h
index 9e06f35..5807da5 100644
--- a/librabbitmq/amqp.h
+++ b/librabbitmq/amqp.h
@@ -396,6 +396,19 @@ RABBITMQ_EXPORT int amqp_encode_table(amqp_bytes_t encoded,
amqp_table_t *input,
size_t *offset);
+struct amqp_connection_info {
+ char *user;
+ char *password;
+ char *host;
+ char *vhost;
+ int port;
+};
+
+RABBITMQ_EXPORT void amqp_default_connection_info(
+ struct amqp_connection_info *parsed);
+RABBITMQ_EXPORT int amqp_parse_url(char *url,
+ struct amqp_connection_info *parsed);
+
#ifdef __cplusplus
}
#endif
diff --git a/librabbitmq/amqp_api.c b/librabbitmq/amqp_api.c
index 4eb7677..d92cccd 100644
--- a/librabbitmq/amqp_api.c
+++ b/librabbitmq/amqp_api.c
@@ -58,6 +58,7 @@ static const char *client_error_strings[ERROR_MAX] = {
"unknown host", /* ERROR_GETHOSTBYNAME_FAILED */
"incompatible AMQP version", /* ERROR_INCOMPATIBLE_AMQP_VERSION */
"connection closed unexpectedly", /* ERROR_CONNECTION_CLOSED */
+ "could not parse AMQP URL", /* ERROR_BAD_AMQP_URL */
};
char *amqp_error_string(int err)
diff --git a/librabbitmq/amqp_private.h b/librabbitmq/amqp_private.h
index 6c15383..e019598 100644
--- a/librabbitmq/amqp_private.h
+++ b/librabbitmq/amqp_private.h
@@ -60,7 +60,8 @@
#define ERROR_GETHOSTBYNAME_FAILED 5
#define ERROR_INCOMPATIBLE_AMQP_VERSION 6
#define ERROR_CONNECTION_CLOSED 7
-#define ERROR_MAX 7
+#define ERROR_BAD_AMQP_URL 8
+#define ERROR_MAX 8
extern char *amqp_os_error_string(int err);
diff --git a/librabbitmq/amqp_url.c b/librabbitmq/amqp_url.c
new file mode 100644
index 0000000..ba1c4f1
--- /dev/null
+++ b/librabbitmq/amqp_url.c
@@ -0,0 +1,205 @@
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License
+ * at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * The Original Code is librabbitmq.
+ *
+ * The Initial Developer of the Original Code is VMware, Inc.
+ * Portions created by VMware are Copyright (c) 2007-2011 VMware, Inc.
+ *
+ * Portions created by Tony Garnock-Jones are Copyright (c) 2009-2010
+ * VMware, Inc. and Tony Garnock-Jones.
+ *
+ * All rights reserved.
+ *
+ * Alternatively, the contents of this file may be used under the terms
+ * of the GNU General Public License Version 2 or later (the "GPL"), in
+ * which case the provisions of the GPL are applicable instead of those
+ * above. If you wish to allow use of your version of this file only
+ * under the terms of the GPL, and not to allow others to use your
+ * version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the
+ * notice and other provisions required by the GPL. If you do not
+ * delete the provisions above, a recipient may use your version of
+ * this file under the terms of any one of the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "amqp.h"
+#include "amqp_framing.h"
+#include "amqp_private.h"
+
+void amqp_default_connection_info(struct amqp_connection_info *ci)
+{
+ /* Apply defaults */
+ ci->user = "guest";
+ ci->password = "guest";
+ ci->host = "localhost";
+ ci->port = 5672;
+ ci->vhost = "/";
+}
+
+/* Scan for the next delimiter, handling percent-encodings on the way. */
+static char find_delim(char **pp, int colon_and_at_sign_are_delims)
+{
+ char *from = *pp;
+ char *to = from;
+
+ for (;;) {
+ char ch = *from++;
+
+ switch (ch) {
+ case ':':
+ case '@':
+ if (!colon_and_at_sign_are_delims) {
+ *to++ = ch;
+ break;
+ }
+
+ /* fall through */
+ case 0:
+ case '/':
+ case '?':
+ case '#':
+ case '[':
+ case ']':
+ *to = 0;
+ *pp = from;
+ return ch;
+
+ case '%': {
+ unsigned int val;
+ int chars;
+ int res = sscanf(from, "%2x%n", &val, &chars);
+
+ if (res == EOF || res < 1 || chars != 2)
+ /* Return a surprising delimiter to
+ force an error. */
+ return '%';
+
+ *to++ = val;
+ from += 2;
+ break;
+ }
+
+ default:
+ *to++ = ch;
+ break;
+ }
+ }
+}
+
+/* Parse an AMQP URL into its component parts. */
+int amqp_parse_url(char *url, struct amqp_connection_info *parsed)
+{
+ int res = -ERROR_BAD_AMQP_URL;
+ char delim;
+ char *start;
+ char *host;
+ char *port = NULL;
+
+ /* check the prefix */
+ if (strncmp(url, "amqp://", 7))
+ goto out;
+
+ host = start = url += 7;
+ delim = find_delim(&url, 1);
+
+ if (delim == ':') {
+ /* The colon could be introducing the port or the
+ password part of the userinfo. We don't know yet,
+ so stash the preceding component. */
+ port = start = url;
+ delim = find_delim(&url, 1);
+ }
+
+ if (delim == '@') {
+ /* What might have been the host and port were in fact
+ the username and password */
+ parsed->user = host;
+ if (port)
+ parsed->password = port;
+
+ port = NULL;
+ host = start = url;
+ delim = find_delim(&url, 1);
+ }
+
+ if (delim == '[') {
+ /* IPv6 address. The bracket should be the first
+ character in the host. */
+ if (host != start || *host != 0)
+ goto out;
+
+ start = url;
+ delim = find_delim(&url, 0);
+
+ if (delim != ']')
+ goto out;
+
+ parsed->host = start;
+ start = url;
+ delim = find_delim(&url, 1);
+
+ /* Closing bracket should be the last character in the
+ host. */
+ if (*start != 0)
+ goto out;
+ }
+ else {
+ /* If we haven't seen the host yet, this is it. */
+ if (*host != 0)
+ parsed->host = host;
+ }
+
+ if (delim == ':') {
+ port = start = url;
+ delim = find_delim(&url, 1);
+ }
+
+ if (port) {
+ char *end;
+ long portnum = strtol(port, &end, 10);
+
+ if (port == end || *end != 0 || portnum < 0 || portnum > 65535)
+ goto out;
+
+ parsed->port = portnum;
+ }
+
+ if (delim == '/') {
+ start = url;
+ delim = find_delim(&url, 1);
+
+ if (delim != 0)
+ goto out;
+
+ parsed->vhost = start;
+ res = 0;
+ }
+ else if (delim == 0) {
+ res = 0;
+ }
+
+ /* Any other delimiter is bad, and we will return
+ ERROR_BAD_AMQP_URL. */
+
+ out:
+ return res;
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 3bd5e6e..ac694dc 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,4 +1,6 @@
-noinst_PROGRAMS = test_tables
+check_PROGRAMS = test_tables test_parse_url
+TESTS = $(check_PROGRAMS)
+EXTRA_DIST = test_tables.expected
AM_CFLAGS = -I$(top_srcdir)/librabbitmq
diff --git a/tests/test_parse_url.c b/tests/test_parse_url.c
new file mode 100644
index 0000000..983d820
--- /dev/null
+++ b/tests/test_parse_url.c
@@ -0,0 +1,174 @@
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License
+ * at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * The Original Code is librabbitmq.
+ *
+ * The Initial Developer of the Original Code is VMware, Inc.
+ * Portions created by VMware are Copyright (c) 2007-2011 VMware, Inc.
+ *
+ * Portions created by Tony Garnock-Jones are Copyright (c) 2009-2010
+ * VMware, Inc. and Tony Garnock-Jones.
+ *
+ * All rights reserved.
+ *
+ * Alternatively, the contents of this file may be used under the terms
+ * of the GNU General Public License Version 2 or later (the "GPL"), in
+ * which case the provisions of the GPL are applicable instead of those
+ * above. If you wish to allow use of your version of this file only
+ * under the terms of the GPL, and not to allow others to use your
+ * version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the
+ * notice and other provisions required by the GPL. If you do not
+ * delete the provisions above, a recipient may use your version of
+ * this file under the terms of any one of the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <inttypes.h>
+
+#include <amqp.h>
+
+static void match_string(const char *what, const char *expect, const char *got)
+{
+ if (strcmp(got, expect)) {
+ fprintf(stderr, "Expected %s '%s', got '%s'\n",
+ what, expect, got);
+ abort();
+ }
+}
+
+static void match_int(const char *what, int expect, int got)
+{
+ if (got != expect) {
+ fprintf(stderr, "Expected %s '%d', got '%d'\n",
+ what, expect, got);
+ abort();
+ }
+}
+
+static void parse_success(const char *url,
+ const char *user,
+ const char *password,
+ const char *host,
+ int port,
+ const char *vhost)
+{
+ char *s = strdup(url);
+ struct amqp_connection_info ci;
+ int res;
+
+ amqp_default_connection_info(&ci);
+ res = amqp_parse_url(s, &ci);
+ if (res) {
+ fprintf(stderr,
+ "Expected to successfully parse URL, but didn't: %s (%s)\n",
+ url, amqp_error_string(-res));
+ abort();
+ }
+
+ match_string("user", user, ci.user);
+ match_string("password", password, ci.password);
+ match_string("host", host, ci.host);
+ match_int("port", port, ci.port);
+ match_string("vhost", vhost, ci.vhost);
+
+ free(s);
+}
+
+static void parse_fail(const char *url)
+{
+ char *s = strdup(url);
+ struct amqp_connection_info ci;
+
+ amqp_default_connection_info(&ci);
+ if (amqp_parse_url(s, &ci) >= 0) {
+ fprintf(stderr,
+ "Expected to fail parsing URL, but didn't: %s\n",
+ url);
+ abort();
+ }
+
+ free(s);
+}
+
+int main(int argc, char **argv)
+{
+ /* From the spec */
+ parse_success("amqp://user:pass@host:10000/vhost", "user", "pass",
+ "host", 10000, "vhost");
+ parse_success("amqp://user%61:%61pass@ho%61st:10000/v%2fhost",
+ "usera", "apass", "hoast", 10000, "v/host");
+ parse_success("amqp://", "guest", "guest", "localhost", 5672, "/");
+ parse_success("amqp://:@/", "", "", "localhost", 5672, "");
+ parse_success("amqp://user@", "user", "guest", "localhost", 5672, "/");
+ parse_success("amqp://user:pass@", "user", "pass",
+ "localhost", 5672, "/");
+ parse_success("amqp://host", "guest", "guest", "host", 5672, "/");
+ parse_success("amqp://:10000", "guest", "guest", "localhost", 10000,
+ "/");
+ parse_success("amqp:///vhost", "guest", "guest", "localhost", 5672,
+ "vhost");
+ parse_success("amqp://host/", "guest", "guest", "host", 5672, "");
+ parse_success("amqp://host/%2f", "guest", "guest", "host", 5672, "/");
+ parse_success("amqp://[::1]", "guest", "guest", "::1", 5672, "/");
+
+ /* Various other success cases */
+ parse_success("amqp://host:100", "guest", "guest", "host", 100, "/");
+ parse_success("amqp://[::1]:100", "guest", "guest", "::1", 100, "/");
+
+ parse_success("amqp://host/blah", "guest", "guest",
+ "host", 5672, "blah");
+ parse_success("amqp://host:100/blah", "guest", "guest",
+ "host", 100, "blah");
+ parse_success("amqp://:100/blah", "guest", "guest",
+ "localhost", 100, "blah");
+ parse_success("amqp://[::1]/blah", "guest", "guest",
+ "::1", 5672, "blah");
+ parse_success("amqp://[::1]:100/blah", "guest", "guest",
+ "::1", 100, "blah");
+
+ parse_success("amqp://user:pass@host", "user", "pass",
+ "host", 5672, "/");
+ parse_success("amqp://user:pass@host:100", "user", "pass",
+ "host", 100, "/");
+ parse_success("amqp://user:pass@:100", "user", "pass",
+ "localhost", 100, "/");
+ parse_success("amqp://user:pass@[::1]", "user", "pass",
+ "::1", 5672, "/");
+ parse_success("amqp://user:pass@[::1]:100", "user", "pass",
+ "::1", 100, "/");
+
+ /* Various failure cases */
+ parse_fail("http://www.rabbitmq.com");
+ parse_fail("amqp://foo:bar:baz");
+ parse_fail("amqp://foo[::1]");
+ parse_fail("amqp://foo:[::1]");
+ parse_fail("amqp://[::1]foo");
+ parse_fail("amqp://foo:1000xyz");
+ parse_fail("amqp://foo:1000000");
+ parse_fail("amqp://foo/bar/baz");
+
+ parse_fail("amqp://foo%1");
+ parse_fail("amqp://foo%1x");
+ parse_fail("amqp://foo%xy");
+
+ return 0;
+}
diff --git a/tests/test_tables.c b/tests/test_tables.c
index 460d10c..4effd6e 100644
--- a/tests/test_tables.c
+++ b/tests/test_tables.c
@@ -39,6 +39,8 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
#include <inttypes.h>
@@ -46,71 +48,175 @@
#include <math.h>
+void die(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+ abort();
+}
+
#define M_PI 3.14159265358979323846264338327
-static void dump_indent(int indent) {
+static void dump_indent(int indent, FILE *out)
+{
int i;
- for (i = 0; i < indent; i++) { putchar(' '); }
+
+ for (i = 0; i < indent; i++)
+ fputc(' ', out);
}
-static void dump_value(int indent, amqp_field_value_t v) {
- dump_indent(indent);
- putchar(v.kind);
+static void dump_value(int indent, amqp_field_value_t v, FILE *out)
+{
+ int i;
+
+ dump_indent(indent, out);
+ fputc(v.kind, out);
+
switch (v.kind) {
- case AMQP_FIELD_KIND_BOOLEAN: puts(v.value.boolean ? " true" : " false"); break;
- case AMQP_FIELD_KIND_I8: printf(" %"PRId8"\n", v.value.i8); break;
- case AMQP_FIELD_KIND_U8: printf(" %"PRIu8"\n", v.value.u8); break;
- case AMQP_FIELD_KIND_I16: printf(" %"PRId16"\n", v.value.i16); break;
- case AMQP_FIELD_KIND_U16: printf(" %"PRIu16"\n", v.value.u16); break;
- case AMQP_FIELD_KIND_I32: printf(" %"PRId32"\n", v.value.i32); break;
- case AMQP_FIELD_KIND_U32: printf(" %"PRIu32"\n", v.value.u32); break;
- case AMQP_FIELD_KIND_I64: printf(" %"PRId64"\n", v.value.i64); break;
- case AMQP_FIELD_KIND_F32: printf(" %g\n", (double) v.value.f32); break;
- case AMQP_FIELD_KIND_F64: printf(" %g\n", v.value.f64); break;
- case AMQP_FIELD_KIND_DECIMAL:
- printf(" %d:::%u\n", v.value.decimal.decimals, v.value.decimal.value); break;
- case AMQP_FIELD_KIND_UTF8:
- printf(" %.*s\n", (int) v.value.bytes.len, (char *) v.value.bytes.bytes); break;
- case AMQP_FIELD_KIND_BYTES:
- {
- int i;
- putchar(' ');
- for (i = 0; i < v.value.bytes.len; i++) {
- printf("%02x", ((char *) v.value.bytes.bytes)[i]);
- }
- putchar('\n');
- }
- break;
- case AMQP_FIELD_KIND_ARRAY:
- putchar('\n');
- {
- int i;
- for (i = 0; i < v.value.array.num_entries; i++) {
- dump_value(indent + 2, v.value.array.entries[i]);
- }
- }
- break;
- case AMQP_FIELD_KIND_TIMESTAMP: printf(" %"PRIu64"\n", v.value.u64); break;
- case AMQP_FIELD_KIND_TABLE:
- putchar('\n');
- {
- int i;
- for (i = 0; i < v.value.table.num_entries; i++) {
- dump_indent(indent + 2);
- printf("%.*s ->\n",
- (int) v.value.table.entries[i].key.len,
- (char *) v.value.table.entries[i].key.bytes);
- dump_value(indent + 4, v.value.table.entries[i].value);
- }
- }
- break;
- case AMQP_FIELD_KIND_VOID: putchar('\n'); break;
- default:
- printf("???\n");
- break;
+ case AMQP_FIELD_KIND_BOOLEAN:
+ fputs(v.value.boolean ? " true\n" : " false\n", out);
+ break;
+
+ case AMQP_FIELD_KIND_I8:
+ fprintf(out, " %"PRId8"\n", v.value.i8);
+ break;
+
+ case AMQP_FIELD_KIND_U8:
+ fprintf(out, " %"PRIu8"\n", v.value.u8);
+ break;
+
+ case AMQP_FIELD_KIND_I16:
+ fprintf(out, " %"PRId16"\n", v.value.i16);
+ break;
+
+ case AMQP_FIELD_KIND_U16:
+ fprintf(out, " %"PRIu16"\n", v.value.u16);
+ break;
+
+ case AMQP_FIELD_KIND_I32:
+ fprintf(out, " %"PRId32"\n", v.value.i32);
+ break;
+
+ case AMQP_FIELD_KIND_U32:
+ fprintf(out, " %"PRIu32"\n", v.value.u32);
+ break;
+
+ case AMQP_FIELD_KIND_I64:
+ fprintf(out, " %"PRId64"\n", v.value.i64);
+ break;
+
+ case AMQP_FIELD_KIND_F32:
+ fprintf(out, " %g\n", (double) v.value.f32);
+ break;
+
+ case AMQP_FIELD_KIND_F64:
+ fprintf(out, " %g\n", v.value.f64);
+ break;
+
+ case AMQP_FIELD_KIND_DECIMAL:
+ fprintf(out, " %d:::%u\n", v.value.decimal.decimals,
+ v.value.decimal.value);
+ break;
+
+ case AMQP_FIELD_KIND_UTF8:
+ fprintf(out, " %.*s\n", (int)v.value.bytes.len,
+ (char *)v.value.bytes.bytes);
+ break;
+
+ case AMQP_FIELD_KIND_BYTES:
+ fputc(' ', out);
+ for (i = 0; i < v.value.bytes.len; i++)
+ fprintf(out, "%02x", ((char *) v.value.bytes.bytes)[i]);
+
+ fputc('\n', out);
+ break;
+
+ case AMQP_FIELD_KIND_ARRAY:
+ fputc('\n', out);
+ for (i = 0; i < v.value.array.num_entries; i++)
+ dump_value(indent + 2, v.value.array.entries[i], out);
+
+ break;
+
+ case AMQP_FIELD_KIND_TIMESTAMP:
+ fprintf(out, " %"PRIu64"\n", v.value.u64);
+ break;
+
+ case AMQP_FIELD_KIND_TABLE:
+ fputc('\n', out);
+ for (i = 0; i < v.value.table.num_entries; i++) {
+ dump_indent(indent + 2, out);
+ fprintf(out, "%.*s ->\n",
+ (int)v.value.table.entries[i].key.len,
+ (char *)v.value.table.entries[i].key.bytes);
+ dump_value(indent + 4, v.value.table.entries[i].value, out);
+ }
+
+ break;
+
+ case AMQP_FIELD_KIND_VOID:
+ fputc('\n', out);
+ break;
+
+ default:
+ fprintf(out, "???\n");
+ break;
}
}
+static void test_dump_value(FILE *out)
+{
+ amqp_table_entry_t entries[8];
+ amqp_table_t table;
+ amqp_field_value_t val;
+
+ entries[0].key = amqp_cstring_bytes("zebra");
+ entries[0].value.kind = AMQP_FIELD_KIND_UTF8;
+ entries[0].value.value.bytes = amqp_cstring_bytes("last");
+
+ entries[1].key = amqp_cstring_bytes("aardvark");
+ entries[1].value.kind = AMQP_FIELD_KIND_UTF8;
+ entries[1].value.value.bytes = amqp_cstring_bytes("first");
+
+ entries[2].key = amqp_cstring_bytes("middle");
+ entries[2].value.kind = AMQP_FIELD_KIND_UTF8;
+ entries[2].value.value.bytes = amqp_cstring_bytes("third");
+
+ entries[3].key = amqp_cstring_bytes("number");
+ entries[3].value.kind = AMQP_FIELD_KIND_I32;
+ entries[3].value.value.i32 = 1234;
+
+ entries[4].key = amqp_cstring_bytes("decimal");
+ entries[4].value.kind = AMQP_FIELD_KIND_DECIMAL;
+ entries[4].value.value.decimal.decimals = 2;
+ entries[4].value.value.decimal.value = 1234;
+
+ entries[5].key = amqp_cstring_bytes("time");
+ entries[5].value.kind = AMQP_FIELD_KIND_TIMESTAMP;
+ entries[5].value.value.u64 = 1234123412341234;
+
+ entries[6].key = amqp_cstring_bytes("beta");
+ entries[6].value.kind = AMQP_FIELD_KIND_UTF8;
+ entries[6].value.value.bytes = amqp_cstring_bytes("second");
+
+ entries[7].key = amqp_cstring_bytes("wombat");
+ entries[7].value.kind = AMQP_FIELD_KIND_UTF8;
+ entries[7].value.value.bytes = amqp_cstring_bytes("fourth");
+
+ table.num_entries = 8;
+ table.entries = entries;
+
+ qsort(table.entries, table.num_entries, sizeof(amqp_table_entry_t), &amqp_table_entry_cmp);
+
+ val.kind = AMQP_FIELD_KIND_TABLE;
+ val.value.table = table;
+
+ dump_value(0, val, out);
+}
+
static uint8_t pre_encoded_table[] = {
0x00, 0x00, 0x00, 0xff, 0x07, 0x6c, 0x6f, 0x6e,
0x67, 0x73, 0x74, 0x72, 0x53, 0x00, 0x00, 0x00,
@@ -147,7 +253,8 @@ static uint8_t pre_encoded_table[] = {
0x44, 0x2d, 0x18
};
-static void test_table_codec(void) {
+static void test_table_codec(FILE *out)
+{
amqp_pool_t pool;
int result;
@@ -239,13 +346,13 @@ static void test_table_codec(void) {
table.num_entries = 14;
table.entries = entries;
- printf("AAAAAAAAAA\n");
+ fprintf(out, "AAAAAAAAAA\n");
{
amqp_field_value_t val;
val.kind = AMQP_FIELD_KIND_TABLE;
val.value.table = table;
- dump_value(0, val);
+ dump_value(0, val, out);
}
init_amqp_pool(&pool, 4096);
@@ -257,21 +364,19 @@ static void test_table_codec(void) {
decoding_bytes.len = sizeof(pre_encoded_table);
decoding_bytes.bytes = pre_encoded_table;
- result = amqp_decode_table(decoding_bytes, &pool, &decoded, &decoding_offset);
- if (result < 0) {
- char *errstr = amqp_error_string(-result);
- printf("Table decoding failed: %d (%s)\n", result, errstr);
- free(errstr);
- abort();
- }
- printf("BBBBBBBBBB\n");
+ result = amqp_decode_table(decoding_bytes, &pool, &decoded,
+ &decoding_offset);
+ if (result < 0)
+ die("Table decoding failed: %s", amqp_error_string(-result));
+
+ fprintf(out, "BBBBBBBBBB\n");
{
amqp_field_value_t val;
val.kind = AMQP_FIELD_KIND_TABLE;
val.value.table = decoded;
- dump_value(0, val);
+ dump_value(0, val, out);
}
}
@@ -285,107 +390,88 @@ static void test_table_codec(void) {
encoding_result.bytes = &encoding_buffer[0];
result = amqp_encode_table(encoding_result, &table, &offset);
- if (result < 0) {
- char *errstr = amqp_error_string(-result);
- printf("Table encoding failed: %d (%s)\n", result, errstr);
- free(errstr);
- abort();
- }
+ if (result < 0)
+ die("Table encoding failed: %s", amqp_error_string(-result));
- if (offset != sizeof(pre_encoded_table)) {
- printf("Offset should be %d, was %d\n", (int) sizeof(pre_encoded_table), (int)offset);
- abort();
- }
+ if (offset != sizeof(pre_encoded_table))
+ die("Offset should be %ld, was %ld", (long)sizeof(pre_encoded_table),
+ (long)offset);
result = memcmp(pre_encoded_table, encoding_buffer, offset);
- if (result != 0) {
- printf("Table encoding differed, result = %d\n", result);
- abort();
- }
+ if (result != 0)
+ die("Table encoding differed", result);
}
empty_amqp_pool(&pool);
}
-int main(int argc, char const * const *argv) {
- amqp_table_entry_t entries[8];
- amqp_table_t table;
+#define CHUNK_SIZE 4096
- union {
- uint32_t i;
- float f;
- } vi;
- union {
- uint64_t l;
- double d;
- } vl;
+static int compare_files(const char *f1, const char *f2)
+{
+ FILE *f1_in;
+ FILE *f2_in;
+ char f1_buf[CHUNK_SIZE];
+ char f2_buf[CHUNK_SIZE];
+ int res;
- entries[0].key = amqp_cstring_bytes("zebra");
- entries[0].value.kind = AMQP_FIELD_KIND_UTF8;
- entries[0].value.value.bytes = amqp_cstring_bytes("last");
+ f1_in = fopen(f1, "r");
+ if (f1_in == NULL)
+ die("opening %s: %s", f1, strerror(errno));
- entries[1].key = amqp_cstring_bytes("aardvark");
- entries[1].value.kind = AMQP_FIELD_KIND_UTF8;
- entries[1].value.value.bytes = amqp_cstring_bytes("first");
+ f2_in = fopen(f2, "r");
+ if (f2_in == NULL)
+ die("opening %s: %s", f2, strerror(errno));
- entries[2].key = amqp_cstring_bytes("middle");
- entries[2].value.kind = AMQP_FIELD_KIND_UTF8;
- entries[2].value.value.bytes = amqp_cstring_bytes("third");
+ for (;;) {
+ size_t f1_got = fread(f1_buf, 1, CHUNK_SIZE, f1_in);
+ size_t f2_got = fread(f2_buf, 1, CHUNK_SIZE, f2_in);
+ res = memcmp(f1_buf, f2_buf, f1_got < f2_got ? f1_got : f2_got);
- entries[3].key = amqp_cstring_bytes("number");
- entries[3].value.kind = AMQP_FIELD_KIND_I32;
- entries[3].value.value.i32 = 1234;
-
- entries[4].key = amqp_cstring_bytes("decimal");
- entries[4].value.kind = AMQP_FIELD_KIND_DECIMAL;
- entries[4].value.value.decimal.decimals = 2;
- entries[4].value.value.decimal.value = 1234;
+ if (res)
+ break;
- entries[5].key = amqp_cstring_bytes("time");
- entries[5].value.kind = AMQP_FIELD_KIND_TIMESTAMP;
- entries[5].value.value.u64 = 1234123412341234;
+ if (f1_got < CHUNK_SIZE || f2_got < CHUNK_SIZE) {
+ if (f1_got != f2_got)
+ res = (f1_got < f2_got ? -1 : 1);
+ break;
+ }
+ }
- entries[6].key = amqp_cstring_bytes("beta");
- entries[6].value.kind = AMQP_FIELD_KIND_UTF8;
- entries[6].value.value.bytes = amqp_cstring_bytes("second");
+ fclose(f1_in);
+ fclose(f2_in);
- entries[7].key = amqp_cstring_bytes("wombat");
- entries[7].value.kind = AMQP_FIELD_KIND_UTF8;
- entries[7].value.value.bytes = amqp_cstring_bytes("fourth");
+ return res;
+}
- table.num_entries = 8;
- table.entries = entries;
+const char *expected_file_name = "test_tables.expected";
- vi.f = M_PI;
- if ((sizeof(float) != 4) || (vi.i != 0x40490fdb)) {
- printf("*** ERROR: single floating point encoding does not work as expected\n");
- printf("sizeof float is %lu, float is %g, u32 is 0x%08lx\n",
- (unsigned long)sizeof(float),
- vi.f,
- (unsigned long) vi.i);
- }
+int main(int argc, char **argv)
+{
+ char *srcdir = getenv("srcdir");
+ char out_path[L_tmpnam];
+ FILE *out = fopen(tmpnam(out_path), "w");
+ char *expected_path;
- vl.d = M_PI;
- if ((sizeof(double) != 8) || (vl.l != 0x400921fb54442d18L)) {
- printf("*** ERROR: double floating point encoding does not work as expected\n");
- printf("sizeof double is %lu, double is %g, u64 is 0x%16"PRIx64"\n",
- (unsigned long)sizeof(double),
- vl.d, vl.l);
- }
+ if (out == NULL)
+ die("opening %s: %s", out_path, strerror(errno));
- test_table_codec();
+ test_table_codec(out);
+ fprintf(out, "----------\n");
+ test_dump_value(out);
- qsort(table.entries, table.num_entries, sizeof(amqp_table_entry_t), &amqp_table_entry_cmp);
+ fclose(out);
- printf("----------\n");
+ if (srcdir == NULL)
+ die("'srcdir' environment variable not defined");
- {
- amqp_field_value_t val;
- val.kind = AMQP_FIELD_KIND_TABLE;
- val.value.table = table;
+ expected_path = malloc(strlen(srcdir) + strlen(expected_file_name) + 2);
+ sprintf(expected_path, "%s/%s", srcdir, expected_file_name);
+ if (compare_files(expected_path, out_path))
+ die("output file did not have expected contents; see %s", out_path);
- dump_value(0, val);
- }
+ if (remove(out_path))
+ die("deleting %s: %s", out_path, strerror(errno));
return 0;
}
diff --git a/tools/common.c b/tools/common.c
index 8277342..81fa332 100644
--- a/tools/common.c
+++ b/tools/common.c
@@ -164,15 +164,21 @@ void die_rpc(amqp_rpc_reply_t r, const char *fmt, ...)
exit(1);
}
-static char *amqp_server = "localhost";
-static char *amqp_vhost = "/";
-static char *amqp_username = "guest";
-static char *amqp_password = "guest";
+static char *amqp_url;
+static char *amqp_server;
+static int amqp_port = -1;
+static char *amqp_vhost;
+static char *amqp_username;
+static char *amqp_password;
const char *connect_options_title = "Connection options";
struct poptOption connect_options[] = {
+ {"url", 'u', POPT_ARG_STRING, &amqp_url, 0,
+ "the AMQP URL to connect to", "amqp://..."},
{"server", 's', POPT_ARG_STRING, &amqp_server, 0,
- "the AMQP server to connect to", "hostname:port"},
+ "the AMQP server to connect to", "hostname"},
+ {"port", 0, POPT_ARG_INT, &amqp_port, 0,
+ "the port to connect on", "port" },
{"vhost", 0, POPT_ARG_STRING, &amqp_vhost, 0,
"the vhost to use when connecting", "vhost"},
{"username", 0, POPT_ARG_STRING, &amqp_username, 0,
@@ -182,39 +188,124 @@ struct poptOption connect_options[] = {
{ NULL, 0, 0, NULL, 0 }
};
+static void init_connection_info(struct amqp_connection_info *ci)
+{
+ struct amqp_connection_info defaults;
+
+ ci->user = NULL;
+ ci->password = NULL;
+ ci->host = NULL;
+ ci->port = -1;
+ ci->vhost = NULL;
+ ci->user = NULL;
+
+ if (amqp_url)
+ die_amqp_error(amqp_parse_url(strdup(amqp_url), ci),
+ "Parsing URL '%s'", amqp_url);
+
+ if (amqp_server) {
+ if (ci->host)
+ die("both --server and --url options specify"
+ " server host");
+
+ /* parse the server string into a hostname and a port */
+ char *colon = strchr(amqp_server, ':');
+ if (colon) {
+ char *port_end;
+ size_t host_len;
+
+ /* Deprecate specifying the port number with the
+ --server option, because it is not ipv6 friendly.
+ --url now allows connection options to be
+ specificied concisely. */
+ fprintf(stderr, "Specifying the port number with"
+ " --server is deprecated\n");
+
+ host_len = colon - amqp_server;
+ ci->host = malloc(host_len + 1);
+ memcpy(ci->host, amqp_server, host_len);
+ ci->host[host_len] = 0;
+
+ if (ci->port >= 0)
+ die("both --server and --url options specify"
+ " server port");
+ if (amqp_port >= 0)
+ die("both --server and --port options specify"
+ " server port");
+
+ ci->port = strtol(colon+1, &port_end, 10);
+ if (ci->port < 0
+ || ci->port > 65535
+ || port_end == colon+1
+ || *port_end != 0)
+ die("bad server port number in '%s'",
+ amqp_server);
+ }
+ }
+
+ if (amqp_port >= 0) {
+ if (ci->port >= 0)
+ die("both --port and --url options specify"
+ " server port");
+
+ ci->port = amqp_port;
+ }
+
+ if (amqp_username) {
+ if (ci->user)
+ die("both --username and --url options specify"
+ " AMQP username");
+
+ ci->user = amqp_username;
+ }
+
+ if (amqp_password) {
+ if (ci->password)
+ die("both --password and --url options specify"
+ " AMQP password");
+
+ ci->password = amqp_password;
+ }
+
+ if (amqp_vhost) {
+ if (ci->vhost)
+ die("both --vhost and --url options specify"
+ " AMQP vhost");
+
+ ci->vhost = amqp_vhost;
+ }
+
+ amqp_default_connection_info(&defaults);
+
+ if (!ci->user)
+ ci->user = defaults.user;
+ if (!ci->password)
+ ci->password = defaults.password;
+ if (!ci->host)
+ ci->host = defaults.host;
+ if (ci->port < 0)
+ ci->port = defaults.port;
+ if (!ci->vhost)
+ ci->vhost = defaults.vhost;
+}
+
amqp_connection_state_t make_connection(void)
{
int s;
+ struct amqp_connection_info ci;
amqp_connection_state_t conn;
- char *host = amqp_server;
- int port = 0;
-
- /* parse the server string into a hostname and a port */
- char *colon = strchr(amqp_server, ':');
- if (colon) {
- char *port_end;
- size_t host_len = colon - amqp_server;
- host = malloc(host_len + 1);
- memcpy(host, amqp_server, host_len);
- host[host_len] = 0;
-
- port = strtol(colon+1, &port_end, 10);
- if (port < 0
- || port > 65535
- || port_end == colon+1
- || *port_end != 0)
- die("bad server port number in %s", amqp_server);
- }
- s = amqp_open_socket(host, port ? port : 5672);
- die_amqp_error(s, "opening socket to %s", amqp_server);
+ init_connection_info(&ci);
+
+ s = amqp_open_socket(ci.host, ci.port);
+ die_amqp_error(s, "opening socket to %s:%d", ci.host, ci.port);
conn = amqp_new_connection();
amqp_set_sockfd(conn, s);
- die_rpc(amqp_login(conn, amqp_vhost, 0, 131072, 0,
+ die_rpc(amqp_login(conn, ci.vhost, 0, 131072, 0,
AMQP_SASL_METHOD_PLAIN,
- amqp_username, amqp_password),
+ ci.user, ci.password),
"logging in to AMQP server");
if (!amqp_channel_open(conn, 1))