diff options
-rw-r--r-- | .hgignore | 1 | ||||
-rw-r--r-- | librabbitmq/Makefile.am | 2 | ||||
-rw-r--r-- | librabbitmq/amqp.h | 13 | ||||
-rw-r--r-- | librabbitmq/amqp_api.c | 1 | ||||
-rw-r--r-- | librabbitmq/amqp_private.h | 3 | ||||
-rw-r--r-- | librabbitmq/amqp_url.c | 205 | ||||
-rw-r--r-- | tests/Makefile.am | 4 | ||||
-rw-r--r-- | tests/test_parse_url.c | 174 | ||||
-rw-r--r-- | tests/test_tables.c | 376 | ||||
-rw-r--r-- | tools/common.c | 147 |
10 files changed, 750 insertions, 176 deletions
@@ -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)) |