<NME> proactor.c
<BEF> ADDFILE
<MSG> PROTON-1470: proactor api - composing and examining addresses

Simpler solution than proposed in the JIRA.

No change to connect()/listen() signatures, they still take a string address.

Added pn_proactor_addr() to format an address string from a separate host and
port, so user does not need to know the format.

Format is simple "host:port", where the port is the substring after the *last*
":". IPv6 hosts are not decorated with "[]", the last ":" in the address is
always the host/port separator, never part of an IPv6 literal.

The C examples now take separate host, port arguments and use pn_proactor_addr()
to construct the proactor address. Existing bindings do not need to be changed,
but it would be good practice to have them use pn_proactor_addr() rather than
hard-coding the "host:port"format.

Also renamed pn_proactor_addr_t to pn_netaddr_t.

<DFF> @@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+/* Common platform-independent implementation for proactor libraries */
+
+#include "proactor-internal.h"
+#include <proton/proactor.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+int pn_proactor_addr(char *buf, size_t len, const char *host, const char *port) {
+  return snprintf(buf, len, "%s:%s", host ? host : "", port ? port : "");
+}
+
+char* pni_split_host_port(char *host_port) {
+  char *port = strrchr(host_port, ':');
+  if (port) {
+    *port = '\0';
+    ++port;
+  } else {
+    port = host_port + strlen(host_port); /* Empty string, point to trailing \0 */
+  }
+  return port;
+}
+

{'commit': 'diff --git a/examples/c/proactor/broker.c b/examples/c/proactor/broker.c\nindex 65019271..05c5552d 100644\n--- a/examples/c/proactor/broker.c\n+++ b/examples/c/proactor/broker.c\n@@ -401,9 +401,13 @@ int main(int argc, char **argv) {\n   queues_init(&b.queues);\n   b.container_id = argv[0];\n   b.threads = 4;\n-  const char *addr = (argc > 1) ? argv[1] : "127.0.0.1:amqp";\n+  int i = 1;\n+  const char *host = (argc > i) ? argv[i++] : "";\n+  const char *port = (argc > i) ? argv[i++] : "amqp";\n \n-  /* Listen on addr */\n+  /* Listenf on addr */\n+  char addr[PN_MAX_ADDR];\n+  pn_proactor_addr(addr, sizeof(addr), host, port);\n   pn_proactor_listen(b.proactor, pn_listener(), addr, 16);\n \n   /* Start n-1 threads */\ndiff --git a/examples/c/proactor/direct.c b/examples/c/proactor/direct.c\nindex f76895c4..c1ac467e 100644\n--- a/examples/c/proactor/direct.c\n+++ b/examples/c/proactor/direct.c\n@@ -34,7 +34,7 @@\n #include <stdlib.h>\n \n typedef struct app_data_t {\n-  const char *connection_address;\n+  const char *host, *port;\n   const char *amqp_address;\n   const char *container_id;\n   int message_count;\n@@ -306,15 +306,19 @@ void run(app_data_t *app) {\n \n int main(int argc, char **argv) {\n   struct app_data_t app = {0};\n-  app.container_id = argv[0];   /* Should be unique */\n-  app.connection_address = (argc > 1) ? argv[1] : "127.0.0.1:amqp";\n-  app.amqp_address = (argc > 2) ? argv[2] : "example";\n-  app.message_count = (argc > 3) ? atoi(argv[3]) : 10;\n+  int i = 0;\n+  app.container_id = argv[i++];   /* Should be unique */\n+  app.host = (argc > 1) ? argv[i++] : "";\n+  app.port = (argc > 1) ? argv[i++] : "amqp";\n+  app.amqp_address = (argc > i) ? argv[i++] : "example";\n+  app.message_count = (argc > i) ? atoi(argv[i++]) : 10;\n \n   /* Create the proactor and connect */\n   app.proactor = pn_proactor();\n   app.listener = pn_listener();\n-  pn_proactor_listen(app.proactor, app.listener, app.connection_address, 16);\n+  char addr[PN_MAX_ADDR];\n+  pn_proactor_addr(addr, sizeof(addr), app.host, app.port);\n+  pn_proactor_listen(app.proactor, app.listener, addr, 16);\n   run(&app);\n   pn_proactor_free(app.proactor);\n   free(app.message_buffer.start);\ndiff --git a/examples/c/proactor/receive.c b/examples/c/proactor/receive.c\nindex c8d3363f..ddf0a35d 100644\n--- a/examples/c/proactor/receive.c\n+++ b/examples/c/proactor/receive.c\n@@ -32,7 +32,7 @@\n #include <stdlib.h>\n \n typedef struct app_data_t {\n-  const char *connection_address;\n+  const char *host, *port;\n   const char *amqp_address;\n   const char *container_id;\n   int message_count;\n@@ -170,14 +170,18 @@ void run(app_data_t *app) {\n \n int main(int argc, char **argv) {\n   struct app_data_t app = {0};\n-  app.container_id = argv[0];   /* Should be unique */\n-  app.connection_address = (argc > 1) ? argv[1] : "127.0.0.1:amqp";\n-  app.amqp_address = (argc > 2) ? argv[2] : "example";\n-  app.message_count = (argc > 3) ? atoi(argv[3]) : 10;\n+  int i = 0;\n+  app.container_id = argv[i++];   /* Should be unique */\n+  app.host = (argc > 1) ? argv[i++] : "";\n+  app.port = (argc > 1) ? argv[i++] : "amqp";\n+  app.amqp_address = (argc > i) ? argv[i++] : "example";\n+  app.message_count = (argc > i) ? atoi(argv[i++]) : 10;\n \n   /* Create the proactor and connect */\n   app.proactor = pn_proactor();\n-  pn_proactor_connect(app.proactor, pn_connection(), app.connection_address);\n+  char addr[PN_MAX_ADDR];\n+  pn_proactor_addr(addr, sizeof(addr), app.host, app.port);\n+  pn_proactor_connect(app.proactor, pn_connection(), addr);\n   run(&app);\n   pn_proactor_free(app.proactor);\n }\ndiff --git a/examples/c/proactor/send.c b/examples/c/proactor/send.c\nindex c21ac68f..33240108 100644\n--- a/examples/c/proactor/send.c\n+++ b/examples/c/proactor/send.c\n@@ -32,7 +32,7 @@\n #include <stdlib.h>\n \n typedef struct app_data_t {\n-  const char *connection_address;\n+  const char *host, *port;\n   const char *amqp_address;\n   const char *container_id;\n   int message_count;\n@@ -178,14 +178,17 @@ void run(app_data_t *app) {\n \n int main(int argc, char **argv) {\n   struct app_data_t app = {0};\n-  app.container_id = argv[0];   /* Should be unique */\n-  app.connection_address = (argc > 1) ? argv[1] : "127.0.0.1:amqp";\n-  app.amqp_address = (argc > 2) ? argv[2] : "example";\n-  app.message_count = (argc > 3) ? atoi(argv[3]) : 10;\n+  int i = 0;\n+  app.container_id = argv[i++];   /* Should be unique */\n+  app.host = (argc > 1) ? argv[i++] : "";\n+  app.port = (argc > 1) ? argv[i++] : "amqp";\n+  app.amqp_address = (argc > i) ? argv[i++] : "example";\n+  app.message_count = (argc > i) ? atoi(argv[i++]) : 10;\n \n-  /* Create the proactor and connect */\n   app.proactor = pn_proactor();\n-  pn_proactor_connect(app.proactor, pn_connection(), app.connection_address);\n+  char addr[PN_MAX_ADDR];\n+  pn_proactor_addr(addr, sizeof(addr), app.host, app.port);\n+  pn_proactor_connect(app.proactor, pn_connection(), addr);\n   run(&app);\n   pn_proactor_free(app.proactor);\n   free(app.message_buffer.start);\ndiff --git a/examples/c/proactor/test.py b/examples/c/proactor/test.py\nindex 4950cef5..f62ea4ed 100644\n--- a/examples/c/proactor/test.py\n+++ b/examples/c/proactor/test.py\n@@ -35,8 +35,8 @@ class Broker(object):\n \n     def __enter__(self):\n         with TestPort() as port:\n-            self.addr = "127.0.0.1:%s" % port\n-            self.proc = self.test.proc(["broker", self.addr])\n+            self.port = port\n+            self.proc = self.test.proc(["broker", "", self.port])\n             self.proc.wait_re("listening")\n             return self\n \n@@ -52,35 +52,34 @@ class CExampleTest(ExampleTestCase):\n     def test_send_receive(self):\n         """Send first then receive"""\n         with Broker(self) as b:\n-            s = self.proc(["send", b.addr])\n+            s = self.proc(["send", "", b.port])\n             self.assertEqual("10 messages sent and acknowledged\\n", s.wait_out())\n-            r = self.proc(["receive", b.addr])\n+            r = self.proc(["receive", "", b.port])\n             self.assertEqual(receive_expect(10), r.wait_out())\n \n     def test_receive_send(self):\n         """Start receiving  first, then send."""\n         with Broker(self) as b:\n-            r = self.proc(["receive", b.addr]);\n-            s = self.proc(["send", b.addr]);\n+            r = self.proc(["receive", "", b.port]);\n+            s = self.proc(["send", "", b.port]);\n             self.assertEqual("10 messages sent and acknowledged\\n", s.wait_out())\n             self.assertEqual(receive_expect(10), r.wait_out())\n \n     def test_send_direct(self):\n         """Send to direct server"""\n         with TestPort() as port:\n-            addr = "127.0.0.1:%s" % port\n-            d = self.proc(["direct", addr])\n+            d = self.proc(["direct", "", port])\n             d.wait_re("listening")\n-            self.assertEqual("10 messages sent and acknowledged\\n", self.proc(["send", addr]).wait_out())\n+            self.assertEqual("10 messages sent and acknowledged\\n", self.proc(["send", "", port]).wait_out())\n             self.assertIn(receive_expect(10), d.wait_out())\n \n     def test_receive_direct(self):\n         """Receive from direct server"""\n         with TestPort() as port:\n             addr = "127.0.0.1:%s" % port\n-            d = self.proc(["direct", addr])\n+            d = self.proc(["direct", "", port])\n             d.wait_re("listening")\n-            self.assertEqual(receive_expect(10), self.proc(["receive", addr]).wait_out())\n+            self.assertEqual(receive_expect(10), self.proc(["receive", "", port]).wait_out())\n             self.assertIn("10 messages sent and acknowledged\\n", d.wait_out())\n \n \ndiff --git a/proton-c/CMakeLists.txt b/proton-c/CMakeLists.txt\nindex b1de9569..03ee5382 100644\n--- a/proton-c/CMakeLists.txt\n+++ b/proton-c/CMakeLists.txt\n@@ -501,7 +501,7 @@ if (PROACTOR STREQUAL "libuv" OR (NOT PROACTOR AND NOT PROACTOR_OK))\n   find_package(Libuv)\n   if (LIBUV_FOUND)\n     set (PROACTOR_OK libuv)\n-    set (qpid-proton-proactor src/proactor/libuv.c)\n+    set (qpid-proton-proactor src/proactor/libuv.c src/proactor/proactor.c)\n     set (PROACTOR_LIBS ${Libuv_LIBRARIES})\n     set_source_files_properties (${qpid-proton-proactor} PROPERTIES\n       # Skip COMPILE_LANGUAGE_FLAGS, libuv.h won\'t compile with --std=c99\ndiff --git a/proton-c/include/proton/netaddr.h b/proton-c/include/proton/netaddr.h\nnew file mode 100644\nindex 00000000..21b79fff\n--- /dev/null\n+++ b/proton-c/include/proton/netaddr.h\n@@ -0,0 +1,76 @@\n+#ifndef PROTON_NETADDR_H\n+#define PROTON_NETADDR_H\n+\n+/*\n+ * Licensed to the Apache Software Foundation (ASF) under one\n+ * or more contributor license agreements.  See the NOTICE file\n+ * distributed with this work for additional information\n+ * regarding copyright ownership.  The ASF licenses this file\n+ * to you under the Apache License, Version 2.0 (the\n+ * "License"); you may not use this file except in compliance\n+ * with the License.  You may obtain a copy of the License at\n+ *\n+ *   http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing,\n+ * software distributed under the License is distributed on an\n+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n+ * KIND, either express or implied.  See the License for the\n+ * specific language governing permissions and limitations\n+ * under the License.\n+ */\n+\n+#include <proton/import_export.h>\n+#include <proton/types.h>\n+\n+/**\n+ * @file\n+ * Use to examine the network address of proactor connections.\n+ *\n+ * @addtogroup proactor\n+ *\n+ * @{\n+ */\n+\n+/**\n+ * Network address of a proactor transport.\n+ */\n+typedef struct pn_netaddr_t pn_netaddr_t;\n+\n+/**\n+ * Format a network address as a human-readable string in buf.\n+ *\n+ * @return the length of the string (excluding trailing \'\\0\'), if >= size then\n+ * the address was truncated.\n+ */\n+PNP_EXTERN int pn_netaddr_str(pn_netaddr_t *addr, char *buf, size_t size);\n+\n+/**\n+ * Get the local address of a transport. Return NULL if not available.\n+ */\n+PNP_EXTERN pn_netaddr_t *pn_netaddr_local(pn_transport_t *t);\n+\n+/**\n+ * Get the remote address of a transport. Return NULL if not available.\n+ */\n+PNP_EXTERN pn_netaddr_t *pn_netaddr_remote(pn_transport_t *t);\n+\n+struct sockaddr;\n+\n+/**\n+ * On POSIX or Windows, get the underlying `struct sockaddr`.\n+ * Return NULL if not available.\n+ */\n+PNP_EXTERN struct sockaddr *pn_netaddr_sockaddr(pn_netaddr_t *na);\n+\n+/**\n+ * On POSIX or Windows, get the size of the underlying `struct sockaddr`.\n+ * Return 0 if not available.\n+ */\n+PNP_EXTERN size_t pn_netaddr_socklen(pn_netaddr_t *na);\n+\n+/**\n+ * @}\n+ */\n+\n+#endif // PROTON_NETADDR_H\ndiff --git a/proton-c/include/proton/proactor.h b/proton-c/include/proton/proactor.h\nindex 8b193b55..4a3b3c7e 100644\n--- a/proton-c/include/proton/proactor.h\n+++ b/proton-c/include/proton/proactor.h\n@@ -20,6 +20,7 @@\n  * under the License.\n  */\n \n+#include <proton/condition.h>\n #include <proton/event.h>\n #include <proton/import_export.h>\n #include <proton/types.h>\n@@ -64,6 +65,23 @@ extern "C" {\n  * @{\n  */\n \n+/**\n+ * Size of buffer that can hold the largest connection or listening address.\n+ */\n+#define PN_MAX_ADDR 1060\n+\n+/**\n+ * Format a host:port address string for pn_proactor_connect() or pn_proactor_listen()\n+ *\n+ * @param[out] addr address is copied to this buffer, with trailing \'\\0\'\n+ * @param[in] size  size of addr buffer\n+ * @param[in] host network host name, DNS name or IP address\n+ * @param[in] port network service name or decimal port number, e.g. "amqp" or "5672"\n+ * @return the length of network address (excluding trailing \'\\0\'), if >= size\n+ * then the address was truncated\n+ */\n+PNP_EXTERN int pn_proactor_addr(char *addr, size_t size, const char *host, const char *port);\n+\n /**\n  * Create a proactor. Must be freed with pn_proactor_free()\n  */\n@@ -80,42 +98,20 @@ PNP_EXTERN void pn_proactor_free(pn_proactor_t *proactor);\n  *\n  * @note Thread safe.\n  *\n- * @param connection @p proactor *takes ownership* of @p connection and will\n+ * @param[in] proactor the proactor object\n+ *\n+ * @param[in] connection @p proactor *takes ownership* of @p connection and will\n  * automatically call pn_connection_free() after the final @ref\n  * PN_TRANSPORT_CLOSED event is handled, or when pn_proactor_free() is\n  * called. You can prevent the automatic free with\n  * pn_proactor_release_connection()\n  *\n- * @param[in] addr the network address in the form "host:port" or as a URL\n- * For a URL *only* the host and port fields are used, the rest is ignored.\n- *\n- * Three special cases are allowed:\n- *\n- * - "host": Connect to "host" on the standard AMQP port (5672).\n- * - ":port": Connect to the local host on "port" using the default protocol.\n- * - "": Connect to the local host on the AMQP port using the default protocol.\n- *\n- * @note The network address @p addr and AMQP address are different things. The\n- * network address enables connection to a remote host, the AMQP address\n- * identifies an AMQP node (such as a queue or topic) *after* you have\n- * established the connection.\n- * The special case ":port" connects to the local host via the default protocol.\n- * The special case "" connects to the local host on the AMQP standard port.\n- *\n- * It is common to combine the two into a URL like this:\n- *\n- *     amqp[s]://user:pass@host:port/amqp_address\n- *\n- * The proactor will extract the host and port only. If you want to use other\n- * fields (e.g. to set up security) you must call the relevant functions on @p\n- * connection before pn_proactor_connect() and handle @ref PN_CONNECTION_BOUND\n- * to set up the @ref transport.\n- *\n- * @note Thread safe.\n- *\n- * @param[in] proactor the proactor object\n+ * @param[in] addr the "host:port" network address, constructed by pn_proactor_addr()\n+ * An empty host will connect to the local host via the default protocol.\n+ * An empty port will connect to the standard AMQP port (5672).\n  *\n  * @param[in] connection @ref connection to be connected to @p addr.\n+ *\n  */\n PNP_EXTERN void pn_proactor_connect(pn_proactor_t *proactor, pn_connection_t *connection, const char *addr);\n \n@@ -131,14 +127,9 @@ PNP_EXTERN void pn_proactor_connect(pn_proactor_t *proactor, pn_connection_t *co\n  * automatically call pn_listener_free() after the final PN_LISTENER_CLOSE event\n  * is handled, or when pn_proactor_free() is called.\n  *\n- * @param[in] addr the network address in the form "host:port" or as a URL\n- * For a URL *only* the host and port fields are used, the rest is ignored.\n- *\n- * Three special cases are allowed:\n- *\n- * - "host": Listen on the standard AMQP port (5672) on the interface and protocol identified by "host"\n- * - ":port": Listen on "port", on all local interfaces, for all protocols.\n- * - "": Listen on the standard AMQP port, on all local interfaces, for all protocols.\n+ * @param[in] addr the "host:port" network address, constructed by pn_proactor_addr()\n+ * An empty host will listen for all protocols on all local interfaces.\n+ * An empty port will listen on the standard AMQP port (5672).\n \n  * @param[in] backlog of un-handled connection requests to allow before refusing\n  * connections. If @p addr resolves to multiple interface/protocol combinations,\n@@ -284,56 +275,6 @@ PNP_EXTERN pn_proactor_t *pn_connection_proactor(pn_connection_t *connection);\n  */\n PNP_EXTERN pn_proactor_t *pn_event_proactor(pn_event_t *event);\n \n-/**\n- * Stores a network address in native format.\n- */\n-typedef struct pn_proactor_addr_t pn_proactor_addr_t;\n-\n-/**\n- * Format a network address as a human-readable string in buf.\n- *\n- * @return the length of the full address string (including trailing NUL). The\n- * string is copied to buf. If the address string is longer than len it is\n- * truncated to len-1 bytes, but the full length is returned.\n- *\n- * If len == 0 the length of the address string is returned, buf is ignored.\n- *\n- * If @p addr is not a pointer to a valid address, buf is set to "" and 0 is returned.\n- *\n- * @note Thread safe.\n- */\n-PNP_EXTERN size_t pn_proactor_addr_str(const pn_proactor_addr_t* addr, char *buf, size_t len);\n-\n-/**\n- * Get the local address of a transport.\n- *\n- * @return NULL if the address is not available. Address is immutable, returned\n- * pointer is valid until @p transport is closed.\n- *\n- * @note Thread safe.\n- */\n-PNP_EXTERN const pn_proactor_addr_t *pn_proactor_addr_local(pn_transport_t* c);\n-\n-/**\n- * Get the remote address of a transport.\n- *\n- * @return NULL if the address is not available. Address is immutable, returned\n- * pointer is valid until @p transport is closed.\n- *\n- * @note Thread safe.\n- */\n-PNP_EXTERN const pn_proactor_addr_t *pn_proactor_addr_remote(pn_transport_t* c);\n-\n-/**\n- * If the proactor implementation uses `struct sockaddr` (for example on POSIX\n- * or Windows sockets) return a pointer to a `struct sockaddr_storage`\n- * containing the address info, otherwise return NULL.\n- *\n- * @note Thread safe.\n- */\n-PNP_EXTERN const struct sockaddr_storage *pn_proactor_addr_sockaddr(const pn_proactor_addr_t *addr);\n-\n-\n /**\n  * Get the real elapsed time since an arbitrary point in the past in milliseconds.\n  *\ndiff --git a/proton-c/src/proactor/epoll.c b/proton-c/src/proactor/epoll.c\nindex 6f9e237c..304644f9 100644\n--- a/proton-c/src/proactor/epoll.c\n+++ b/proton-c/src/proactor/epoll.c\n@@ -1727,22 +1727,22 @@ void pn_proactor_disconnect(pn_proactor_t *p, pn_condition_t *cond) {\n     wake_notify(&p->context);\n }\n \n-const struct sockaddr_storage *pn_proactor_addr_sockaddr(const pn_proactor_addr_t *addr) {\n+const struct sockaddr_storage *pn_netaddr_sockaddr(const pn_netaddr_t *addr) {\n   assert(false);\n   return NULL;\n }\n \n-const struct pn_proactor_addr_t *pn_proactor_addr_local(pn_transport_t *t) {\n+const struct pn_netaddr_t *pn_netaddr_local(pn_transport_t *t) {\n   assert(false);\n   return NULL;\n }\n \n-const struct pn_proactor_addr_t *pn_proactor_addr_remote(pn_transport_t *t) {\n+const struct pn_netaddr_t *pn_netaddr_remote(pn_transport_t *t) {\n   assert(false);\n   return NULL;\n }\n \n-size_t pn_proactor_addr_str(const struct pn_proactor_addr_t* addr, char *buf, size_t len) {\n+size_t pn_netaddr_str(const struct pn_netaddr_t* addr, char *buf, size_t len) {\n   struct sockaddr_storage *sa = (struct sockaddr_storage*)addr;\n   char host[NI_MAXHOST];\n   char port[NI_MAXSERV];\ndiff --git a/proton-c/src/proactor/libuv.c b/proton-c/src/proactor/libuv.c\nindex 5a7f97dd..14682c43 100644\n--- a/proton-c/src/proactor/libuv.c\n+++ b/proton-c/src/proactor/libuv.c\n@@ -20,13 +20,14 @@\n  */\n \n #include "../core/log_private.h"\n-#include "../core/url-internal.h"\n+#include "proactor-internal.h"\n \n #include <proton/condition.h>\n #include <proton/connection_driver.h>\n #include <proton/engine.h>\n #include <proton/listener.h>\n #include <proton/message.h>\n+#include <proton/netaddr.h>\n #include <proton/object.h>\n #include <proton/proactor.h>\n #include <proton/transport.h>\n@@ -132,11 +133,9 @@ static void work_init(work_t* w, pn_proactor_t* p, struct_type type) {\n \n /* ================ IO ================ */\n \n-#define MAXADDR (NI_MAXHOST+NI_MAXSERV)\n-\n /* A resolvable address */\n typedef struct addr_t {\n-  char addr[MAXADDR];\n+  char host_port[PN_MAX_ADDR];\n   char *host, *port;            /* Point into addr after destructive pni_url_parse */\n   uv_getaddrinfo_t getaddrinfo; /* UV getaddrinfo request, contains list of addrinfo */\n   struct addrinfo* addrinfo;    /* The current addrinfo being tried */\n@@ -153,6 +152,10 @@ PN_STRUCT_CLASSDEF(lsocket, CID_pn_listener_socket)\n \n typedef enum { W_NONE, W_PENDING, W_CLOSED } wake_state;\n \n+struct pn_netaddr_t {\n+  struct sockaddr_storage ss;\n+};\n+\n /* An incoming or outgoing connection. */\n typedef struct pconnection_t {\n   work_t work;                  /* Must be first to allow casting */\n@@ -170,7 +173,7 @@ typedef struct pconnection_t {\n \n   lsocket_t *lsocket;         /* Incoming connection only */\n \n-  struct sockaddr_storage local, remote; /* Actual addresses */\n+  struct pn_netaddr_t local, remote; /* Actual addresses */\n   uv_timer_t timer;\n   uv_write_t write;\n   size_t writing;               /* size of pending write request, 0 if none pending */\n@@ -277,9 +280,9 @@ static void work_start(work_t *w) {\n }\n \n static void parse_addr(addr_t *addr, const char *str) {\n-  strncpy(addr->addr, str, sizeof(addr->addr));\n-  char *scheme, *user, *pass, *path;\n-  pni_parse_url(addr->addr, &scheme, &user, &pass, &addr->host, &addr->port, &path);\n+  strncpy(addr->host_port, str, sizeof(addr->host_port));\n+  addr->host = addr->host_port;\n+  addr->port = pni_split_host_port(addr->host_port);\n }\n \n /* Make a pn_class for pconnection_t since it is attached to a pn_connection_t record */\n@@ -313,12 +316,10 @@ static pconnection_t *pconnection(pn_proactor_t *p, pn_connection_t *c, bool ser\n   if (server) {\n     pn_transport_set_server(pc->driver.transport);\n   }\n-  pc->addr.host = pc->addr.port = pc->addr.addr; /* Set host/port to "" by default */\n   pn_record_t *r = pn_connection_attachments(pc->driver.connection);\n   pn_record_def(r, PN_PROACTOR, &pconnection_class);\n   pn_record_set(r, PN_PROACTOR, pc);\n   pn_decref(pc);                /* Will be deleted when the connection is */\n-  pc->addr.host = pc->addr.port = pc->addr.addr; /* Set host/port to "" by default */\n   return pc;\n }\n \n@@ -388,7 +389,8 @@ static void on_close_pconnection_final(uv_handle_t *h) {\n }\n \n static void uv_safe_close(uv_handle_t *h, uv_close_cb cb) {\n-  if (!uv_is_closing(h)) {\n+  /* Only close if h has been initialized and is not already closing */\n+  if (h->type && !uv_is_closing(h)) {\n     uv_close(h, cb);\n   }\n }\n@@ -1267,25 +1269,29 @@ void pn_listener_accept(pn_listener_t *l, pn_connection_t *c) {\n   work_notify(&l->work);\n }\n \n-const struct sockaddr_storage *pn_proactor_addr_sockaddr(const pn_proactor_addr_t *addr) {\n-  return (const struct sockaddr_storage*)addr;\n+struct sockaddr *pn_netaddr_sockaddr(pn_netaddr_t *na) {\n+  return (struct sockaddr*)na;\n+}\n+\n+size_t pn_netaddr_socklen(pn_netaddr_t *na) {\n+  return sizeof(struct sockaddr_storage);\n }\n \n-const struct pn_proactor_addr_t *pn_proactor_addr_local(pn_transport_t *t) {\n+pn_netaddr_t *pn_netaddr_local(pn_transport_t *t) {\n   pconnection_t *pc = get_pconnection(pn_transport_connection(t));\n-  return pc ? (pn_proactor_addr_t*)&pc->local : NULL;\n+  return pc? &pc->local : NULL;\n }\n \n-const struct pn_proactor_addr_t *pn_proactor_addr_remote(pn_transport_t *t) {\n+pn_netaddr_t *pn_netaddr_remote(pn_transport_t *t) {\n   pconnection_t *pc = get_pconnection(pn_transport_connection(t));\n-  return pc ? (pn_proactor_addr_t*)&pc->remote : NULL;\n+  return pc ? &pc->remote : NULL;\n }\n \n-size_t pn_proactor_addr_str(const struct pn_proactor_addr_t* addr, char *buf, size_t len) {\n-  struct sockaddr_storage *sa = (struct sockaddr_storage*)addr;\n+int pn_netaddr_str(struct pn_netaddr_t* na, char *buf, size_t len) {\n   char host[NI_MAXHOST];\n   char port[NI_MAXSERV];\n-  int err = getnameinfo((struct sockaddr *)sa, sizeof(*sa), host, sizeof(host), port, sizeof(port),\n+  int err = getnameinfo((struct sockaddr *)&na->ss, sizeof(na->ss),\n+                        host, sizeof(host), port, sizeof(port),\n                         NI_NUMERICHOST | NI_NUMERICSERV);\n   if (!err) {\n     return snprintf(buf, len, "%s:%s", host, port); /* FIXME aconway 2017-03-29: ipv6 format? */\ndiff --git a/proton-c/src/proactor/proactor-internal.h b/proton-c/src/proactor/proactor-internal.h\nnew file mode 100644\nindex 00000000..bf79cd50\n--- /dev/null\n+++ b/proton-c/src/proactor/proactor-internal.h\n@@ -0,0 +1,37 @@\n+#ifndef PROACTOR_NETADDR_INTERNAL_H\n+#define PROACTOR_NETADDR_INTERNAL_H\n+\n+/*\n+ * Licensed to the Apache Software Foundation (ASF) under one\n+ * or more contributor license agreements.  See the NOTICE file\n+ * distributed with this work for additional information\n+ * regarding copyright ownership.  The ASF licenses this file\n+ * to you under the Apache License, Version 2.0 (the\n+ * "License"); you may not use this file except in compliance\n+ * with the License.  You may obtain a copy of the License at\n+ *\n+ *   http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing,\n+ * software distributed under the License is distributed on an\n+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n+ * KIND, either express or implied.  See the License for the\n+ * specific language governing permissions and limitations\n+ * under the License.\n+ */\n+\n+/*\n+ * Given a "host:port" string, split it in-place like so:\n+ *\n+ *     char *host = host_port;\n+ *     char *port = pn_split_host_port(host_port);\n+ *\n+ * Note this modifies the original host_port string by replacing the last \':\'\n+ * character with \'\\0\'.\n+ *\n+ * If there is no \':\', the returned pointer is an empty string, not NULL.\n+ *\n+ */\n+char* pni_split_host_port(char *host_port);\n+\n+#endif // PROACTOR_NETADDR_INTERNAL_H\ndiff --git a/proton-c/src/proactor/proactor.c b/proton-c/src/proactor/proactor.c\nnew file mode 100644\nindex 00000000..a7939e8b\n--- /dev/null\n+++ b/proton-c/src/proactor/proactor.c\n@@ -0,0 +1,45 @@\n+/*\n+ * Licensed to the Apache Software Foundation (ASF) under one\n+ * or more contributor license agreements.  See the NOTICE file\n+ * distributed with this work for additional information\n+ * regarding copyright ownership.  The ASF licenses this file\n+ * to you under the Apache License, Version 2.0 (the\n+ * "License"); you may not use this file except in compliance\n+ * with the License.  You may obtain a copy of the License at\n+ *\n+ *   http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing,\n+ * software distributed under the License is distributed on an\n+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n+ * KIND, either express or implied.  See the License for the\n+ * specific language governing permissions and limitations\n+ * under the License.\n+ */\n+\n+\n+/* Common platform-independent implementation for proactor libraries */\n+\n+#include "proactor-internal.h"\n+#include <proton/proactor.h>\n+\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+\n+\n+int pn_proactor_addr(char *buf, size_t len, const char *host, const char *port) {\n+  return snprintf(buf, len, "%s:%s", host ? host : "", port ? port : "");\n+}\n+\n+char* pni_split_host_port(char *host_port) {\n+  char *port = strrchr(host_port, \':\');\n+  if (port) {\n+    *port = \'\\0\';\n+    ++port;\n+  } else {\n+    port = host_port + strlen(host_port); /* Empty string, point to trailing \\0 */\n+  }\n+  return port;\n+}\n+\ndiff --git a/proton-c/src/tests/proactor.c b/proton-c/src/tests/proactor.c\nindex 13657454..8cf0d260 100644\n--- a/proton-c/src/tests/proactor.c\n+++ b/proton-c/src/tests/proactor.c\n@@ -23,6 +23,7 @@\n #include <proton/connection.h>\n #include <proton/event.h>\n #include <proton/listener.h>\n+#include <proton/netaddr.h>\n #include <proton/proactor.h>\n #include <proton/ssl.h>\n #include <proton/transport.h>\n@@ -521,7 +522,7 @@ static void test_ipv4_ipv6(test_t *t) {\n   pn_proactor_t *client = pts[0].proactor, *server = pts[1].proactor;\n \n   /* Listen on all interfaces for IPv6 only. If this fails, skip IPv6 tests */\n-  test_port_t port6 = test_port("[::]");\n+  test_port_t port6 = test_port("::");\n   pn_listener_t *l6 = pn_listener();\n   pn_proactor_listen(server, l6, port6.host_port, 4);\n   TEST_ETYPE_EQUAL(t, PN_LISTENER_OPEN, PROACTOR_TEST_RUN(pts));\n@@ -573,12 +574,12 @@ static void test_ipv4_ipv6(test_t *t) {\n   EXPECT_CONNECT(port, "");          /* local->all */\n \n   if (has_ipv6) {\n-    EXPECT_CONNECT(port6, "[::]"); /* v6->v6 */\n+    EXPECT_CONNECT(port6, "::"); /* v6->v6 */\n     EXPECT_CONNECT(port6, "");     /* local->v6 */\n-    EXPECT_CONNECT(port, "[::1]"); /* v6->all */\n+    EXPECT_CONNECT(port, "::1"); /* v6->all */\n \n     EXPECT_FAIL(port6, "127.0.0.1"); /* fail v4->v6 */\n-    EXPECT_FAIL(port4, "[::1]");     /* fail v6->v4 */\n+    EXPECT_FAIL(port4, "::1");     /* fail v6->v4 */\n   }\n   PROACTOR_TEST_DRAIN(pts);\n \n@@ -696,22 +697,37 @@ static void test_ssl(test_t *t) {\n   PROACTOR_TEST_FREE(pts);\n }\n \n+static void test_proactor_addr(test_t *t) {\n+  /* Test the address formatter */\n+  char addr[PN_MAX_ADDR];\n+  pn_proactor_addr(addr, sizeof(addr), "foo", "bar");\n+  TEST_STR_EQUAL(t, "foo:bar", addr);\n+  pn_proactor_addr(addr, sizeof(addr), "foo", "");\n+  TEST_STR_EQUAL(t, "foo:", addr);\n+  pn_proactor_addr(addr, sizeof(addr), "foo", NULL);\n+  TEST_STR_EQUAL(t, "foo:", addr);\n+  pn_proactor_addr(addr, sizeof(addr), "", "bar");\n+  TEST_STR_EQUAL(t, ":bar", addr);\n+  pn_proactor_addr(addr, sizeof(addr), NULL, "bar");\n+  TEST_STR_EQUAL(t, ":bar", addr);\n+  pn_proactor_addr(addr, sizeof(addr), "1:2:3:4", "5");\n+  TEST_STR_EQUAL(t, "1:2:3:4:5", addr);\n+  pn_proactor_addr(addr, sizeof(addr), "1:2:3:4", "");\n+  TEST_STR_EQUAL(t, "1:2:3:4:", addr);\n+  pn_proactor_addr(addr, sizeof(addr), "1:2:3:4", NULL);\n+  TEST_STR_EQUAL(t, "1:2:3:4:", addr);\n+}\n \n /* Test pn_proactor_addr funtions */\n \n /* FIXME aconway 2017-03-30: windows will need winsock2.h etc.\n-   These headers are *only* needed for test_addr and only for the getnameinfo part.\n+   These headers are *only* needed for test_netaddr and only for the getnameinfo part.\n    This is the only non-portable part of the proactor test suite.\n    */\n #include <sys/socket.h>         /* For socket_storage */\n #include <netdb.h>              /* For NI_MAXHOST/NI_MAXSERV */\n \n-static void test_addr(test_t *t) {\n-  /* Make sure NULL addr gives empty string */\n-  char str[1024] = "not-empty";\n-  pn_proactor_addr_str(NULL, str, sizeof(str));\n-  TEST_STR_EQUAL(t, "", str);\n-\n+static void test_netaddr(test_t *t) {\n   proactor_test_t pts[] ={ { open_wake_handler }, { listen_handler } };\n   PROACTOR_TEST_INIT(pts, t);\n   pn_proactor_t *client = pts[0].proactor, *server = pts[1].proactor;\n@@ -727,35 +743,35 @@ static void test_addr(test_t *t) {\n   char cr[1024], cl[1024], sr[1024], sl[1024];\n \n   pn_transport_t *ct = pn_connection_transport(c);\n-  pn_proactor_addr_str(pn_proactor_addr_remote(ct), cr, sizeof(cr));\n+  pn_netaddr_str(pn_netaddr_remote(ct), cr, sizeof(cr));\n   TEST_STR_IN(t, test_port_use_host(&port, ""), cr); /* remote address has listening port */\n \n   pn_connection_t *s = last_accepted; /* server side of the connection */\n   pn_transport_t *st = pn_connection_transport(s);\n   if (!TEST_CHECK(t, st)) return;\n-  pn_proactor_addr_str(pn_proactor_addr_local(st), sl, sizeof(sl));\n+  pn_netaddr_str(pn_netaddr_local(st), sl, sizeof(sl));\n   TEST_STR_EQUAL(t, cr, sl);  /* client remote == server local */\n \n-  pn_proactor_addr_str(pn_proactor_addr_local(ct), cl, sizeof(cl));\n-  pn_proactor_addr_str(pn_proactor_addr_remote(st), sr, sizeof(sr));\n+  pn_netaddr_str(pn_netaddr_local(ct), cl, sizeof(cl));\n+  pn_netaddr_str(pn_netaddr_remote(st), sr, sizeof(sr));\n   TEST_STR_EQUAL(t, cl, sr);    /* client local == server remote */\n \n   /* Examine as sockaddr */\n-  const struct sockaddr_storage* addr = pn_proactor_addr_sockaddr(pn_proactor_addr_remote(ct));\n-  TEST_CHECK(t, AF_INET == addr->ss_family);\n+  pn_netaddr_t *na = pn_netaddr_remote(ct);\n+  struct sockaddr *sa = pn_netaddr_sockaddr(na);\n+  TEST_CHECK(t, AF_INET == sa->sa_family);\n   char host[NI_MAXHOST] = "";\n   char serv[NI_MAXSERV] = "";\n-  int err = getnameinfo((struct sockaddr*)addr, sizeof(*addr),\n-                        host, sizeof(host),\n-                        serv, sizeof(serv),\n+  int err = getnameinfo(sa, pn_netaddr_socklen(na),\n+                        host, sizeof(host), serv, sizeof(serv),\n                         NI_NUMERICHOST | NI_NUMERICSERV);\n   TEST_CHECK(t, 0 == err);\n   TEST_STR_EQUAL(t, "127.0.0.1", host);\n   TEST_STR_EQUAL(t, port.str, serv);\n \n   /* Make sure you can use NULL, 0 to get length of address string without a crash */\n-  size_t len = pn_proactor_addr_str(pn_proactor_addr_local(ct), NULL, 0);\n-  TEST_CHECK(t, strlen(cl) == len);\n+  size_t len = pn_netaddr_str(pn_netaddr_local(ct), NULL, 0);\n+  TEST_CHECKF(t, strlen(cl) == len, "%d != %d", strlen(cl), len);\n \n   sock_close(port.sock);\n   PROACTOR_TEST_DRAIN(pts);\n@@ -848,7 +864,8 @@ int main(int argc, char **argv) {\n   RUN_ARGV_TEST(failed, t, test_ipv4_ipv6(&t));\n   RUN_ARGV_TEST(failed, t, test_release_free(&t));\n   RUN_ARGV_TEST(failed, t, test_ssl(&t));\n-  RUN_ARGV_TEST(failed, t, test_addr(&t));\n+  RUN_ARGV_TEST(failed, t, test_proactor_addr(&t));\n+  RUN_ARGV_TEST(failed, t, test_netaddr(&t));\n   RUN_ARGV_TEST(failed, t, test_disconnect(&t));\n   RUN_ARGV_TEST(failed, t, test_abort(&t));\n   RUN_ARGV_TEST(failed, t, test_refuse(&t));\n', 'commit_subject': 'PROTON-1470: proactor api - composing and examining addresses', 'repo_name': 'apache/qpid-proton', 'committer': {'name': 'Alan Conway', 'email': 'f52ac7f18aee66f8da492e36fb0a920cfff785a4@redhat.com', 'time_sec': '1493419659', 'tz_offset': '-240', 'date': {'seconds': '1493419659'}}, 'commit_tree': '4f54cf46c4f6f16d7bd9c9c8164ab48f049b461d', 'commit_message': 'PROTON-1470: proactor api - composing and examining addresses\n\nSimpler solution than proposed in the JIRA.\n\nNo change to connect()/listen() signatures, they still take a string address.\n\nAdded pn_proactor_addr() to format an address string from a separate host and\nport, so user does not need to know the format.\n\nFormat is simple "host:port", where the port is the substring after the *last*\n":". IPv6 hosts are not decorated with "[]", the last ":" in the address is\nalways the host/port separator, never part of an IPv6 literal.\n\nThe C examples now take separate host, port arguments and use pn_proactor_addr()\nto construct the proactor address. Existing bindings do not need to be changed,\nbut it would be good practice to have them use pn_proactor_addr() rather than\nhard-coding the "host:port"format.\n\nAlso renamed pn_proactor_addr_t to pn_netaddr_t.\n', 'language_name': 'C++', 'license': 'apache-2.0', 'hunks': '@@ -0,0 +1,45 @@\n+/*\n+ * Licensed to the Apache Software Foundation (ASF) under one\n+ * or more contributor license agreements.  See the NOTICE file\n+ * distributed with this work for additional information\n+ * regarding copyright ownership.  The ASF licenses this file\n+ * to you under the Apache License, Version 2.0 (the\n+ * "License"); you may not use this file except in compliance\n+ * with the License.  You may obtain a copy of the License at\n+ *\n+ *   http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing,\n+ * software distributed under the License is distributed on an\n+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n+ * KIND, either express or implied.  See the License for the\n+ * specific language governing permissions and limitations\n+ * under the License.\n+ */\n+\n+\n+/* Common platform-independent implementation for proactor libraries */\n+\n+#include "proactor-internal.h"\n+#include <proton/proactor.h>\n+\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+\n+\n+int pn_proactor_addr(char *buf, size_t len, const char *host, const char *port) {\n+  return snprintf(buf, len, "%s:%s", host ? host : "", port ? port : "");\n+}\n+\n+char* pni_split_host_port(char *host_port) {\n+  char *port = strrchr(host_port, \':\');\n+  if (port) {\n+    *port = \'\\0\';\n+    ++port;\n+  } else {\n+    port = host_port + strlen(host_port); /* Empty string, point to trailing \\0 */\n+  }\n+  return port;\n+}\n+\n', 'addition_count': 45, 'deletion_count': 0, 'src_file': '/dev/null', 'tgt_file': 'b/proton-c/src/proactor/proactor.c', 'file_extension': '.c', 'before_file': 'ADDFILE', 'message': 'PROTON-1470: proactor api - composing and examining addresses\n\nSimpler solution than proposed in the JIRA.\n\nNo change to connect()/listen() signatures, they still take a string address.\n\nAdded pn_proactor_addr() to format an address string from a separate host and\nport, so user does not need to know the format.\n\nFormat is simple "host:port", where the port is the substring after the *last*\n":". IPv6 hosts are not decorated with "[]", the last ":" in the address is\nalways the host/port separator, never part of an IPv6 literal.\n\nThe C examples now take separate host, port arguments and use pn_proactor_addr()\nto construct the proactor address. Existing bindings do not need to be changed,\nbut it would be good practice to have them use pn_proactor_addr() rather than\nhard-coding the "host:port"format.\n\nAlso renamed pn_proactor_addr_t to pn_netaddr_t.\n', 'author_name': 'Alan Conway', 'lang': 'c'}