blob: 3e4365a3aaa8c32aebc06b8fb94ee1dd43cf4469 [file] [log] [blame]
/*
* 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.
*/
#include "../src/proactor/proactor-internal.h"
#include "./pn_test_proactor.hpp"
#include "./test_config.h"
#include <proton/condition.h>
#include <proton/connection.h>
#include <proton/delivery.h>
#include <proton/event.h>
#include <proton/link.h>
#include <proton/listener.h>
#include <proton/netaddr.h>
#include <proton/proactor.h>
#include <proton/session.h>
#include <proton/ssl.h>
#include <proton/transport.h>
#include <string.h>
#include <iostream>
using namespace pn_test;
using Catch::Matchers::Contains;
using Catch::Matchers::Equals;
/* Test that interrupt and timeout events cause pn_proactor_wait() to return. */
TEST_CASE("proactor_interrupt_timeout") {
proactor p;
CHECK(pn_proactor_get(p) == NULL); /* idle */
pn_proactor_interrupt(p);
CHECK(PN_PROACTOR_INTERRUPT == p.wait_next());
CHECK(pn_proactor_get(p) == NULL); /* idle */
/* Set an immediate timeout */
pn_proactor_set_timeout(p, 0);
CHECK(PN_PROACTOR_TIMEOUT == p.wait_next());
CHECK(PN_PROACTOR_INACTIVE == p.wait_next());
/* Set a (very short) timeout */
pn_proactor_set_timeout(p, 1);
CHECK(PN_PROACTOR_TIMEOUT == p.wait_next());
CHECK(PN_PROACTOR_INACTIVE == p.wait_next());
/* Set and cancel a timeout, make sure we don't get the timeout event */
pn_proactor_set_timeout(p, 10000000);
pn_proactor_cancel_timeout(p);
CHECK(PN_PROACTOR_INACTIVE == p.wait_next());
CHECK(pn_proactor_get(p) == NULL); /* idle */
}
namespace {
class common_handler : public handler {
handler *accept_; // Handler for accepted connections
public:
common_handler(handler *accept = 0) : accept_(accept) {}
bool handle(pn_event_t *e) CATCH_OVERRIDE {
switch (pn_event_type(e)) {
/* Always stop on these noteworthy events */
case PN_TRANSPORT_ERROR:
case PN_LISTENER_OPEN:
case PN_LISTENER_CLOSE:
case PN_PROACTOR_INACTIVE:
return true;
case PN_LISTENER_ACCEPT:
listener = pn_event_listener(e);
connection = pn_connection();
if (accept_) pn_connection_set_context(connection, accept_);
pn_listener_accept2(listener, connection, NULL);
return false;
// Return remote opens
case PN_CONNECTION_REMOTE_OPEN:
pn_connection_open(pn_event_connection(e));
return false;
case PN_SESSION_REMOTE_OPEN:
pn_session_open(pn_event_session(e));
return false;
case PN_LINK_REMOTE_OPEN:
pn_link_open(pn_event_link(e));
return false;
// Return remote closes
case PN_CONNECTION_REMOTE_CLOSE:
pn_connection_close(pn_event_connection(e));
return false;
case PN_SESSION_REMOTE_CLOSE:
pn_session_close(pn_event_session(e));
return false;
case PN_LINK_REMOTE_CLOSE:
pn_link_close(pn_event_link(e));
return false;
default:
return false;
}
}
};
/* close a connection when it is remote open */
struct close_on_open_handler : public common_handler {
bool handle(pn_event_t *e) CATCH_OVERRIDE {
switch (pn_event_type(e)) {
case PN_CONNECTION_REMOTE_OPEN:
pn_connection_close(pn_event_connection(e));
return false;
default:
return common_handler::handle(e);
}
}
};
} // namespace
/* Test simple client/server connection that opens and closes */
TEST_CASE("proactor_connect") {
close_on_open_handler h;
proactor p(&h);
/* Connect and wait for close at both ends */
pn_listener_t *l = p.listen(":0", &h);
REQUIRE_RUN(p, PN_LISTENER_OPEN);
p.connect(l);
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED);
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED);
}
namespace {
/* Return on connection open, close and return on wake */
struct close_on_wake_handler : public common_handler {
bool handle(pn_event_t *e) CATCH_OVERRIDE {
switch (pn_event_type(e)) {
case PN_CONNECTION_WAKE:
pn_connection_close(pn_event_connection(e));
return true;
default:
return common_handler::handle(e);
}
}
};
} // namespace
// Test waking up a connection that is idle
TEST_CASE("proactor_connection_wake") {
common_handler h;
proactor p(&h);
close_on_wake_handler wh;
pn_listener_t *l = p.listen();
REQUIRE_RUN(p, PN_LISTENER_OPEN);
pn_connection_t *c = p.connect(l, &wh);
pn_incref(c); /* Keep a reference for wake() after free */
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
CHECK(!pn_proactor_get(p)); /* Should be idle */
pn_connection_wake(c);
REQUIRE_RUN(p, PN_CONNECTION_WAKE);
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED);
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED); /* Both ends */
/* Verify we don't get a wake after close even if they happen together */
pn_connection_t *c2 = p.connect(l, &wh);
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
pn_connection_wake(c2);
pn_proactor_disconnect(p, NULL);
pn_connection_wake(c2);
for (pn_event_type_t et = p.run(); et != PN_PROACTOR_INACTIVE; et = p.run()) {
switch (et) {
case PN_TRANSPORT_ERROR:
case PN_TRANSPORT_CLOSED:
case PN_LISTENER_CLOSE:
break; // expected
default:
FAIL("Unexpected event type: " << et);
}
}
// The pn_connection_t is still valid so wake is legal but a no-op.
// Make sure there's no memory error.
pn_connection_wake(c);
pn_decref(c);
}
namespace {
struct abort_handler : public common_handler {
bool handle(pn_event_t *e) {
switch (pn_event_type(e)) {
case PN_CONNECTION_REMOTE_OPEN:
/* Close the transport - abruptly closes the socket */
pn_transport_close_tail(pn_connection_transport(pn_event_connection(e)));
pn_transport_close_head(pn_connection_transport(pn_event_connection(e)));
return false;
default:
return common_handler::handle(e);
}
}
};
} // namespace
/* Verify that pn_transport_close_head/tail aborts a connection without an AMQP
* protocol close */
TEST_CASE("proactor_abort") {
abort_handler sh; // Handle listener and server side of connection
proactor p(&sh);
pn_listener_t *l = p.listen();
REQUIRE_RUN(p, PN_LISTENER_OPEN);
common_handler ch; // Handle client side of connection
pn_connection_t *c = p.connect(l, &ch);
/* server transport closes */
REQUIRE_RUN(p, PN_TRANSPORT_ERROR);
CHECK_THAT(*sh.last_condition,
cond_matches("amqp:connection:framing-error", "abort"));
/* client transport closes */
REQUIRE_RUN(p, PN_TRANSPORT_ERROR);
CHECK_THAT(*ch.last_condition,
cond_matches("amqp:connection:framing-error", "abort"));
pn_listener_close(l);
REQUIRE_RUN(p, PN_LISTENER_CLOSE);
REQUIRE_RUN(p, PN_PROACTOR_INACTIVE);
/* Verify expected event sequences, no unexpected events */
CHECK_THAT(ETYPES(PN_CONNECTION_INIT, PN_CONNECTION_LOCAL_OPEN,
PN_CONNECTION_BOUND, PN_TRANSPORT_TAIL_CLOSED,
PN_TRANSPORT_ERROR, PN_TRANSPORT_HEAD_CLOSED,
PN_TRANSPORT_CLOSED),
Equals(ch.log_clear()));
CHECK_THAT(ETYPES(PN_LISTENER_OPEN, PN_LISTENER_ACCEPT, PN_CONNECTION_INIT,
PN_CONNECTION_BOUND, PN_CONNECTION_REMOTE_OPEN,
PN_TRANSPORT_TAIL_CLOSED, PN_TRANSPORT_ERROR,
PN_TRANSPORT_HEAD_CLOSED, PN_TRANSPORT_CLOSED,
PN_LISTENER_CLOSE, PN_PROACTOR_INACTIVE),
Equals(sh.log_clear()));
}
/* Test that INACTIVE event is generated when last connections/listeners closes.
*/
TEST_CASE("proactor_inactive") {
close_on_wake_handler h;
proactor p(&h);
/* Listen, connect, disconnect */
pn_listener_t *l = p.listen();
REQUIRE_RUN(p, PN_LISTENER_OPEN);
pn_connection_t *c = p.connect(l, &h);
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
pn_connection_wake(c);
REQUIRE_RUN(p, PN_CONNECTION_WAKE);
/* Expect TRANSPORT_CLOSED both ends */
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED);
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED);
pn_listener_close(l);
REQUIRE_RUN(p, PN_LISTENER_CLOSE);
/* Immediate timer generates INACTIVE (no connections) */
pn_proactor_set_timeout(p, 0);
REQUIRE_RUN(p, PN_PROACTOR_TIMEOUT);
REQUIRE_RUN(p, PN_PROACTOR_INACTIVE);
/* Connect, set-timer, disconnect */
l = p.listen();
REQUIRE_RUN(p, PN_LISTENER_OPEN);
c = p.connect(l, &h);
pn_proactor_set_timeout(p, 1000000);
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
pn_connection_wake(c);
REQUIRE_RUN(p, PN_CONNECTION_WAKE);
/* Expect TRANSPORT_CLOSED from client and server */
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED);
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED);
pn_listener_close(l);
REQUIRE_RUN(p, PN_LISTENER_CLOSE);
/* No INACTIVE till timer is cancelled */
CHECK(!pn_proactor_get(p)); // idle
pn_proactor_cancel_timeout(p);
REQUIRE_RUN(p, PN_PROACTOR_INACTIVE);
}
/* Tests for error handling */
TEST_CASE("proactor_errors") {
close_on_wake_handler h;
proactor p(&h);
/* Invalid connect/listen service name */
p.connect("127.0.0.1:xxx");
REQUIRE_RUN(p, PN_TRANSPORT_ERROR);
CHECK_THAT(*h.last_condition, cond_matches("proton:io", "xxx"));
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED);
REQUIRE_RUN(p, PN_PROACTOR_INACTIVE);
pn_listener_t *l = pn_listener();
pn_proactor_listen(p, l, "127.0.0.1:xxx", 1);
REQUIRE_RUN(p, PN_LISTENER_CLOSE);
CHECK_THAT(*h.last_condition, cond_matches("proton:io", "xxx"));
REQUIRE_RUN(p, PN_PROACTOR_INACTIVE);
/* Invalid connect/listen host name */
p.connect("nosuch.example.com:");
REQUIRE_RUN(p, PN_TRANSPORT_ERROR);
CHECK_THAT(*h.last_condition, cond_matches("proton:io", "nosuch"));
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED);
REQUIRE_RUN(p, PN_PROACTOR_INACTIVE);
pn_proactor_listen(p, pn_listener(), "nosuch.example.com:", 1);
REQUIRE_RUN(p, PN_LISTENER_CLOSE);
CHECK_THAT(*h.last_condition, cond_matches("proton:io", "nosuch"));
REQUIRE_RUN(p, PN_PROACTOR_INACTIVE);
/* Listen on a port already in use */
l = p.listen();
REQUIRE_RUN(p, PN_LISTENER_OPEN);
std::string laddr = ":" + listening_port(l);
p.listen(laddr);
REQUIRE_RUN(p, PN_LISTENER_CLOSE);
CHECK_THAT(*h.last_condition, cond_matches("proton:io"));
pn_listener_close(l);
REQUIRE_RUN(p, PN_LISTENER_CLOSE);
REQUIRE_RUN(p, PN_PROACTOR_INACTIVE);
/* Connect with no listener */
p.connect(laddr);
REQUIRE_RUN(p, PN_TRANSPORT_ERROR);
CHECK_THAT(*h.last_condition, cond_matches("proton:io", "refused"));
}
namespace {
/* Closing the connection during PN_TRANSPORT_ERROR should be a no-op
* Regression test for: https://issues.apache.org/jira/browse/PROTON-1586
*/
struct transport_close_connection_handler : public common_handler {
bool handle(pn_event_t *e) {
switch (pn_event_type(e)) {
case PN_TRANSPORT_ERROR:
pn_connection_close(pn_event_connection(e));
break;
default:
return common_handler::handle(e);
}
return PN_EVENT_NONE;
}
};
} // namespace
/* Closing the connection during PN_TRANSPORT_ERROR due to connection failure
* should be a no-op. Regression test for:
* https://issues.apache.org/jira/browse/PROTON-1586
*/
TEST_CASE("proactor_proton_1586") {
transport_close_connection_handler h;
proactor p(&h);
p.connect(":yyy");
REQUIRE_RUN(p, PN_TRANSPORT_ERROR);
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED);
CHECK_THAT(*h.last_condition, cond_matches("proton:io", ":yyy"));
// No events expected after PN_TRANSPORT_CLOSED, proactor is inactive.
CHECK(PN_PROACTOR_INACTIVE == p.wait_next());
}
/* Test that we can control listen/select on ipv6/v4 and listen on both by
* default */
TEST_CASE("proactor_ipv4_ipv6") {
close_on_open_handler h;
proactor p(&h);
/* Listen on all interfaces for IPv4 only. */
pn_listener_t *l4 = p.listen("0.0.0.0:0");
REQUIRE_RUN(p, PN_LISTENER_OPEN);
/* Empty address listens on both IPv4 and IPv6 on all interfaces */
pn_listener_t *l = p.listen();
REQUIRE_RUN(p, PN_LISTENER_OPEN);
#define EXPECT_CONNECT(LISTENER, HOST) \
do { \
p.connect(std::string(HOST) + ":" + listening_port(LISTENER)); \
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED); \
CHECK_THAT(*h.last_condition, cond_empty()); \
} while (0)
EXPECT_CONNECT(l4, "127.0.0.1"); /* v4->v4 */
EXPECT_CONNECT(l4, ""); /* local->v4*/
EXPECT_CONNECT(l, "127.0.0.1"); /* v4->all */
EXPECT_CONNECT(l, ""); /* local->all */
/* Listen on ipv6 loopback, if it fails skip ipv6 tests.
NOTE: Don't use the unspecified address "::" here - ipv6-disabled platforms
may allow listening on "::" without complaining. However they won't have a
local ipv6 loopback configured, so "::1" will force an error.
*/
pn_listener_t *l6 = pn_listener();
pn_proactor_listen(p, l6, "::1:0", 4);
pn_event_type_t e = p.run();
if (e == PN_LISTENER_OPEN && !pn_condition_is_set(h.last_condition)) {
EXPECT_CONNECT(l6, "::1"); /* v6->v6 */
EXPECT_CONNECT(l6, ""); /* local->v6 */
EXPECT_CONNECT(l, "::1"); /* v6->all */
pn_listener_close(l6);
} else {
WARN("skip IPv6 tests: %s %s" << e << *h.last_condition);
}
pn_listener_close(l);
pn_listener_close(l4);
}
/* Make sure we clean up released connections and open sockets
* correctly */
TEST_CASE("proactor_release_free") {
common_handler h;
proactor p(&h);
pn_listener_t *l = p.listen();
REQUIRE_RUN(p, PN_LISTENER_OPEN);
/* leave one connection to the proactor */
pn_connection_t *c = p.connect(l);
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
{
/* release c1 and free immediately */
auto_free<pn_connection_t, pn_connection_free> c1(p.connect(l));
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
pn_proactor_release_connection(c1);
}
REQUIRE_RUN(p, PN_TRANSPORT_ERROR);
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED);
/* release c2 and but don't free till after proactor free */
auto_free<pn_connection_t, pn_connection_free> c2(p.connect(l));
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
pn_proactor_release_connection(c2);
REQUIRE_RUN(p, PN_TRANSPORT_ERROR);
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED);
// OK to free a listener/connection that was never used by a
// proactor.
pn_listener_free(pn_listener());
pn_connection_free(pn_connection());
}
#define SSL_FILE(NAME) CMAKE_CURRENT_SOURCE_DIR "/ssl-certs/" NAME
#define SSL_PW "tserverpw"
/* Windows vs. OpenSSL certificates */
#if defined(_WIN32)
#define CERTIFICATE(NAME) SSL_FILE(NAME "-certificate.p12")
#define SET_CREDENTIALS(DOMAIN, NAME) \
pn_ssl_domain_set_credentials(DOMAIN, SSL_FILE(NAME "-full.p12"), "", SSL_PW)
#else
#define CERTIFICATE(NAME) SSL_FILE(NAME "-certificate.pem")
#define SET_CREDENTIALS(DOMAIN, NAME) \
pn_ssl_domain_set_credentials(DOMAIN, CERTIFICATE(NAME), \
SSL_FILE(NAME "-private-key.pem"), SSL_PW)
#endif
namespace {
struct ssl_handler : public common_handler {
auto_free<pn_ssl_domain_t, pn_ssl_domain_free> ssl_domain;
ssl_handler(pn_ssl_domain_t *d) : ssl_domain(d) {}
bool handle(pn_event_t *e) {
switch (pn_event_type(e)) {
case PN_CONNECTION_BOUND:
CHECK(0 == pn_ssl_init(pn_ssl(pn_event_transport(e)), ssl_domain, NULL));
return false;
case PN_CONNECTION_REMOTE_OPEN: {
pn_ssl_t *ssl = pn_ssl(pn_event_transport(e));
CHECK(ssl);
char protocol[256];
CHECK(pn_ssl_get_protocol_name(ssl, protocol, sizeof(protocol)));
CHECK_THAT(protocol, Contains("TLS"));
pn_connection_t *c = pn_event_connection(e);
if (pn_connection_state(c) & PN_LOCAL_ACTIVE) {
pn_connection_close(c); // Client closes on completion.
} else {
pn_connection_open(c); // Server returns the OPEN
}
return true;
}
default:
return common_handler::handle(e);
}
}
};
} // namespace
/* Test various SSL connections between proactors*/
TEST_CASE("proactor_ssl") {
if (!pn_ssl_present()) {
WARN("Skip SSL tests, not available");
return;
}
ssl_handler client(pn_ssl_domain(PN_SSL_MODE_CLIENT));
ssl_handler server(pn_ssl_domain(PN_SSL_MODE_SERVER));
CHECK(0 == SET_CREDENTIALS(server.ssl_domain, "tserver"));
proactor p;
common_handler listener(&server); // Use server for accepted connections
pn_listener_t *l = p.listen(":0", &listener);
REQUIRE_RUN(p, PN_LISTENER_OPEN);
/* Basic SSL connection */
p.connect(l, &client);
/* Open ok at both ends */
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
CHECK_THAT(*server.last_condition, cond_empty());
CHECK_THAT(*client.last_condition, cond_empty());
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED);
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED);
/* Verify peer with good hostname */
pn_ssl_domain_t *cd = client.ssl_domain;
REQUIRE(0 == pn_ssl_domain_set_trusted_ca_db(cd, CERTIFICATE("tserver")));
REQUIRE(0 == pn_ssl_domain_set_peer_authentication(
cd, PN_SSL_VERIFY_PEER_NAME, NULL));
pn_connection_t *c = pn_connection();
pn_connection_set_hostname(c, "test_server");
p.connect(l, &client, c);
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
CHECK_THAT(*server.last_condition, cond_empty());
CHECK_THAT(*client.last_condition, cond_empty());
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED);
REQUIRE_RUN(p, PN_TRANSPORT_CLOSED);
/* Verify peer with bad hostname */
c = pn_connection();
pn_connection_set_hostname(c, "wrongname");
p.connect(l, &client, c);
REQUIRE_RUN(p, PN_TRANSPORT_ERROR);
CHECK_THAT(*client.last_condition,
cond_matches("amqp:connection:framing-error", "SSL"));
}
TEST_CASE("proactor_addr") {
/* Test the address formatter */
char addr[PN_MAX_ADDR];
pn_proactor_addr(addr, sizeof(addr), "foo", "bar");
CHECK_THAT("foo:bar", Equals(addr));
pn_proactor_addr(addr, sizeof(addr), "foo", "");
CHECK_THAT("foo:", Equals(addr));
pn_proactor_addr(addr, sizeof(addr), "foo", NULL);
CHECK_THAT("foo:", Equals(addr));
pn_proactor_addr(addr, sizeof(addr), "", "bar");
CHECK_THAT(":bar", Equals(addr));
pn_proactor_addr(addr, sizeof(addr), NULL, "bar");
CHECK_THAT(":bar", Equals(addr));
pn_proactor_addr(addr, sizeof(addr), "1:2:3:4", "5");
CHECK_THAT("1:2:3:4:5", Equals(addr));
pn_proactor_addr(addr, sizeof(addr), "1:2:3:4", "");
CHECK_THAT("1:2:3:4:", Equals(addr));
pn_proactor_addr(addr, sizeof(addr), "1:2:3:4", NULL);
CHECK_THAT("1:2:3:4:", Equals(addr));
}
/* Test pn_proactor_addr functions */
TEST_CASE("proactor_netaddr") {
common_handler h;
proactor p(&h);
/* Use IPv4 to get consistent results all platforms */
pn_listener_t *l = p.listen("127.0.0.1:0");
REQUIRE_RUN(p, PN_LISTENER_OPEN);
pn_connection_t *c = p.connect(l);
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
REQUIRE_RUN(p, PN_CONNECTION_REMOTE_OPEN);
// client remote, client local, server remote and server
// local address strings
char cr[1024], cl[1024], sr[1024], sl[1024];
pn_transport_t *ct = pn_connection_transport(c);
const pn_netaddr_t *na = pn_transport_remote_addr(ct);
pn_netaddr_str(na, cr, sizeof(cr));
CHECK_THAT(cr, Contains(listening_port(l)));
pn_connection_t *s = h.connection; /* server side of the connection */
pn_transport_t *st = pn_connection_transport(s);
pn_netaddr_str(pn_transport_local_addr(st), sl, sizeof(sl));
CHECK_THAT(cr, Equals(sl)); /* client remote == server local */
pn_netaddr_str(pn_transport_local_addr(ct), cl, sizeof(cl));
pn_netaddr_str(pn_transport_remote_addr(st), sr, sizeof(sr));
CHECK_THAT(cl, Equals(sr)); /* client local == server remote */
char host[PN_MAX_ADDR] = "";
char serv[PN_MAX_ADDR] = "";
CHECK(0 == pn_netaddr_host_port(na, host, sizeof(host), serv, sizeof(serv)));
CHECK_THAT("127.0.0.1", Equals(host));
CHECK(listening_port(l) == serv);
/* Make sure you can use NULL, 0 to get length of address
* string without a crash */
size_t len = pn_netaddr_str(pn_transport_local_addr(ct), NULL, 0);
CHECK(strlen(cl) == len);
}
TEST_CASE("proactor_parse_addr") {
char buf[1024];
const char *host, *port;
CHECK(0 == pni_parse_addr("foo:bar", buf, sizeof(buf), &host, &port));
CHECK_THAT("foo", Equals(host));
CHECK_THAT("bar", Equals(port));
CHECK(0 == pni_parse_addr("foo:", buf, sizeof(buf), &host, &port));
CHECK_THAT("foo", Equals(host));
CHECK_THAT("5672", Equals(port));
CHECK(0 == pni_parse_addr(":bar", buf, sizeof(buf), &host, &port));
CHECK(NULL == host);
CHECK_THAT("bar", Equals(port));
CHECK(0 == pni_parse_addr(":", buf, sizeof(buf), &host, &port));
CHECK(NULL == host);
CHECK_THAT("5672", Equals(port));
CHECK(0 == pni_parse_addr(":amqps", buf, sizeof(buf), &host, &port));
CHECK_THAT("5671", Equals(port));
CHECK(0 == pni_parse_addr(":amqp", buf, sizeof(buf), &host, &port));
CHECK_THAT("5672", Equals(port));
CHECK(0 == pni_parse_addr("::1:2:3", buf, sizeof(buf), &host, &port));
CHECK_THAT("::1:2", Equals(host));
CHECK_THAT("3", Equals(port));
CHECK(0 == pni_parse_addr(":::", buf, sizeof(buf), &host, &port));
CHECK_THAT("::", Equals(host));
CHECK_THAT("5672", Equals(port));
CHECK(0 == pni_parse_addr("", buf, sizeof(buf), &host, &port));
CHECK(NULL == host);
CHECK_THAT("5672", Equals(port));
}
/* Test pn_proactor_disconnect */
TEST_CASE("proactor_disconnect") {
common_handler ch, sh;
proactor client(&ch), server(&sh);
// Start two listeners on the server
pn_listener_t *l = server.listen();
REQUIRE_RUN(server, PN_LISTENER_OPEN);
pn_listener_t *l2 = server.listen();
REQUIRE_RUN(server, PN_LISTENER_OPEN);
// Two connections from client
pn_connection_t *c = client.connect(l);
CHECK_CORUN(client, server, PN_CONNECTION_REMOTE_OPEN);
pn_connection_t *c2 = client.connect(l);
CHECK_CORUN(client, server, PN_CONNECTION_REMOTE_OPEN);
/* Disconnect the client proactor */
auto_free<pn_condition_t, pn_condition_free> cond(pn_condition());
pn_condition_format(cond, "test-name", "test-description");
pn_proactor_disconnect(client, cond);
/* Verify expected client side first */
CHECK_CORUN(client, server, PN_TRANSPORT_ERROR);
CHECK_THAT(*client.handler->last_condition,
cond_matches("test-name", "test-description"));
CHECK_CORUN(client, server, PN_TRANSPORT_ERROR);
CHECK_THAT(*client.handler->last_condition,
cond_matches("test-name", "test-description"));
REQUIRE_RUN(client, PN_PROACTOR_INACTIVE);
/* Now check server sees the disconnects */
CHECK_CORUN(server, client, PN_TRANSPORT_ERROR);
CHECK_THAT(*server.handler->last_condition,
cond_matches("amqp:connection:framing-error", "aborted"));
CHECK_CORUN(server, client, PN_TRANSPORT_ERROR);
CHECK_THAT(*server.handler->last_condition,
cond_matches("amqp:connection:framing-error", "aborted"));
/* Now disconnect the server end (the listeners) */
pn_proactor_disconnect(server, NULL);
REQUIRE_RUN(server, PN_LISTENER_CLOSE);
REQUIRE_RUN(server, PN_LISTENER_CLOSE);
REQUIRE_RUN(server, PN_PROACTOR_INACTIVE);
/* Make sure the proactors are still functional */
pn_listener_t *l3 = server.listen();
REQUIRE_RUN(server, PN_LISTENER_OPEN);
client.connect(l3);
CHECK_CORUN(client, server, PN_CONNECTION_REMOTE_OPEN);
}
namespace {
const size_t FRAME = 512; /* Smallest legal frame */
const ssize_t CHUNK = (FRAME + FRAME / 2); /* Chunk overflows frame */
const size_t BODY = (CHUNK * 3 + CHUNK / 2); /* Body doesn't fit into chunks */
} // namespace
struct message_stream_handler : public common_handler {
pn_link_t *sender;
pn_delivery_t *dlv;
pn_rwbytes_t send_buf, recv_buf;
ssize_t size, sent, received;
bool complete;
message_stream_handler()
: sender(), dlv(), send_buf(), recv_buf(), size(), sent(), received(),
complete() {}
bool handle(pn_event_t *e) {
switch (pn_event_type(e)) {
case PN_CONNECTION_BOUND:
pn_transport_set_max_frame(pn_event_transport(e), FRAME);
return false;
case PN_SESSION_INIT:
pn_session_set_incoming_capacity(pn_event_session(e),
FRAME); /* Single frame incoming */
pn_session_set_outgoing_window(pn_event_session(e),
1); /* Single frame outgoing */
return false;
case PN_LINK_REMOTE_OPEN:
common_handler::handle(e);
if (pn_link_is_receiver(pn_event_link(e))) {
pn_link_flow(pn_event_link(e), 1);
} else {
sender = pn_event_link(e);
}
return false;
case PN_LINK_FLOW: /* Start a delivery */
if (pn_link_is_sender(pn_event_link(e)) && !dlv) {
dlv = pn_delivery(pn_event_link(e), pn_dtag("x", 1));
}
return false;
case PN_CONNECTION_WAKE: { /* Send a chunk */
ssize_t remains = size - sent;
ssize_t n = (CHUNK < remains) ? CHUNK : remains;
CHECK(n == pn_link_send(sender, send_buf.start + sent, n));
sent += n;
if (sent == size) {
CHECK(pn_link_advance(sender));
}
return false;
}
case PN_DELIVERY: { /* Receive a delivery - smaller than a
chunk? */
pn_delivery_t *dlv = pn_event_delivery(e);
if (pn_delivery_readable(dlv)) {
ssize_t n = pn_delivery_pending(dlv);
rwbytes_ensure(&recv_buf, received + n);
REQUIRE(n ==
pn_link_recv(pn_event_link(e), recv_buf.start + received, n));
received += n;
}
complete = !pn_delivery_partial(dlv);
return true;
}
default:
return common_handler::handle(e);
}
}
};
/* Test sending/receiving a message in chunks */
TEST_CASE("proactor_message_stream") {
message_stream_handler h;
proactor p(&h);
pn_listener_t *l = p.listen();
REQUIRE_RUN(p, PN_LISTENER_OPEN);
/* Encode a large (not very) message to send in chunks */
auto_free<pn_message_t, pn_message_free> m(pn_message());
pn_data_put_binary(pn_message_body(m), pn_bytes(std::string(BODY, 'x')));
h.size = pn_message_encode2(m, &h.send_buf);
pn_connection_t *c = p.connect(l);
pn_session_t *ssn = pn_session(c);
pn_session_open(ssn);
pn_link_t *snd = pn_sender(ssn, "x");
pn_link_open(snd);
REQUIRE_RUN(p, PN_LINK_FLOW);
/* Send and receive the message in chunks */
do {
pn_connection_wake(c); /* Initiate send/receive of one chunk */
do { /* May be multiple receives for one send */
REQUIRE_RUN(p, PN_DELIVERY);
} while (h.received < h.sent);
} while (!h.complete);
CHECK(h.received == h.size);
CHECK(h.sent == h.size);
CHECK(!memcmp(h.send_buf.start, h.recv_buf.start, h.size));
free(h.send_buf.start);
free(h.recv_buf.start);
}