aboutsummaryrefslogtreecommitdiff
path: root/src/sp/protocol/pair1
diff options
context:
space:
mode:
Diffstat (limited to 'src/sp/protocol/pair1')
-rw-r--r--src/sp/protocol/pair1/CMakeLists.txt20
-rw-r--r--src/sp/protocol/pair1/pair.c540
-rw-r--r--src/sp/protocol/pair1/pair1_poly.c535
-rw-r--r--src/sp/protocol/pair1/pair1_poly_test.c370
-rw-r--r--src/sp/protocol/pair1/pair1_test.c433
5 files changed, 1898 insertions, 0 deletions
diff --git a/src/sp/protocol/pair1/CMakeLists.txt b/src/sp/protocol/pair1/CMakeLists.txt
new file mode 100644
index 00000000..12e12607
--- /dev/null
+++ b/src/sp/protocol/pair1/CMakeLists.txt
@@ -0,0 +1,20 @@
+#
+# Copyright 2019 Staysail Systems, Inc. <info@staystail.tech>
+# Copyright 2018 Capitar IT Group BV <info@capitar.com>
+#
+# This software is supplied under the terms of the MIT License, a
+# copy of which should be located in the distribution where this
+# file was obtained (LICENSE.txt). A copy of the license may also be
+# found online at https://opensource.org/licenses/MIT.
+#
+
+# PAIRv1 protocol
+nng_directory(pair1)
+
+# XXX: break pair1_poly into an ifdef.
+nng_sources_if(NNG_PROTO_PAIR1 pair.c pair1_poly.c)
+nng_headers_if(NNG_PROTO_PAIR1 nng/protocol/pair1/pair.h)
+nng_defines_if(NNG_PROTO_PAIR1 NNG_HAVE_PAIR1)
+
+nng_test(pair1_test)
+nng_test(pair1_poly_test) \ No newline at end of file
diff --git a/src/sp/protocol/pair1/pair.c b/src/sp/protocol/pair1/pair.c
new file mode 100644
index 00000000..ba497c42
--- /dev/null
+++ b/src/sp/protocol/pair1/pair.c
@@ -0,0 +1,540 @@
+//
+// Copyright 2020 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2018 Capitar IT Group BV <info@capitar.com>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#include <stdlib.h>
+
+#include "core/nng_impl.h"
+#include "nng/protocol/pair1/pair.h"
+
+// Pair protocol. The PAIRv1 protocol is a simple 1:1 messaging pattern.
+
+#ifdef NNG_ENABLE_STATS
+#define BUMP_STAT(x) nni_stat_inc(x, 1)
+#else
+#define BUMP_STAT(x)
+#endif
+
+typedef struct pair1_pipe pair1_pipe;
+typedef struct pair1_sock pair1_sock;
+
+static void pair1_pipe_send_cb(void *);
+static void pair1_pipe_recv_cb(void *);
+static void pair1_pipe_get_cb(void *);
+static void pair1_pipe_put_cb(void *);
+static void pair1_pipe_fini(void *);
+
+// pair1_sock is our per-socket protocol private structure.
+struct pair1_sock {
+ nni_msgq * uwq;
+ nni_msgq * urq;
+ nni_sock * sock;
+ bool raw;
+ pair1_pipe * p;
+ nni_atomic_int ttl;
+ nni_mtx mtx;
+#ifdef NNG_ENABLE_STATS
+ nni_stat_item stat_poly;
+ nni_stat_item stat_raw;
+ nni_stat_item stat_reject_mismatch;
+ nni_stat_item stat_reject_already;
+ nni_stat_item stat_ttl_drop;
+ nni_stat_item stat_rx_malformed;
+ nni_stat_item stat_tx_malformed;
+ nni_stat_item stat_tx_drop;
+#endif
+#ifdef NNG_TEST_LIB
+ bool inject_header;
+#endif
+};
+
+// pair1_pipe is our per-pipe protocol private structure.
+struct pair1_pipe {
+ nni_pipe * pipe;
+ pair1_sock *pair;
+ nni_aio aio_send;
+ nni_aio aio_recv;
+ nni_aio aio_get;
+ nni_aio aio_put;
+};
+
+static void
+pair1_sock_fini(void *arg)
+{
+ pair1_sock *s = arg;
+
+ nni_mtx_fini(&s->mtx);
+}
+
+#ifdef NNG_ENABLE_STATS
+static void
+pair1_add_sock_stat(
+ pair1_sock *s, nni_stat_item *item, const nni_stat_info *info)
+{
+ nni_stat_init(item, info);
+ nni_sock_add_stat(s->sock, item);
+}
+#endif
+
+static int
+pair1_sock_init_impl(void *arg, nni_sock *sock, bool raw)
+{
+ pair1_sock *s = arg;
+
+ // Raw mode uses this.
+ nni_mtx_init(&s->mtx);
+ s->sock = sock;
+
+#ifdef NNG_ENABLE_STATS
+ static const nni_stat_info poly_info = {
+ .si_name = "poly",
+ .si_desc = "polyamorous mode?",
+ .si_type = NNG_STAT_BOOLEAN,
+ };
+ static const nni_stat_info raw_info = {
+ .si_name = "raw",
+ .si_desc = "raw mode?",
+ .si_type = NNG_STAT_BOOLEAN,
+ };
+ static const nni_stat_info mismatch_info = {
+ .si_name = "mismatch",
+ .si_desc = "pipes rejected (protocol mismatch)",
+ .si_type = NNG_STAT_COUNTER,
+ .si_atomic = true,
+ };
+ static const nni_stat_info already_info = {
+ .si_name = "already",
+ .si_desc = "pipes rejected (already connected)",
+ .si_type = NNG_STAT_COUNTER,
+ .si_atomic = true,
+ };
+ static const nni_stat_info ttl_drop_info = {
+ .si_name = "ttl_drop",
+ .si_desc = "messages dropped due to too many hops",
+ .si_type = NNG_STAT_COUNTER,
+ .si_unit = NNG_UNIT_MESSAGES,
+ .si_atomic = true,
+ };
+ static const nni_stat_info tx_drop_info = {
+ .si_name = "tx_drop",
+ .si_desc = "messages dropped undeliverable",
+ .si_type = NNG_STAT_COUNTER,
+ .si_unit = NNG_UNIT_MESSAGES,
+ .si_atomic = true,
+ };
+ static const nni_stat_info rx_malformed_info = {
+ .si_name = "rx_malformed",
+ .si_desc = "malformed messages received",
+ .si_type = NNG_STAT_COUNTER,
+ .si_unit = NNG_UNIT_MESSAGES,
+ .si_atomic = true,
+ };
+ static const nni_stat_info tx_malformed_info = {
+ .si_name = "tx_malformed",
+ .si_desc = "malformed messages not sent",
+ .si_type = NNG_STAT_COUNTER,
+ .si_unit = NNG_UNIT_MESSAGES,
+ .si_atomic = true,
+ };
+
+ pair1_add_sock_stat(s, &s->stat_poly, &poly_info);
+ pair1_add_sock_stat(s, &s->stat_raw, &raw_info);
+ pair1_add_sock_stat(s, &s->stat_reject_mismatch, &mismatch_info);
+ pair1_add_sock_stat(s, &s->stat_reject_already, &already_info);
+ pair1_add_sock_stat(s, &s->stat_ttl_drop, &ttl_drop_info);
+ pair1_add_sock_stat(s, &s->stat_tx_drop, &tx_drop_info);
+ pair1_add_sock_stat(s, &s->stat_rx_malformed, &rx_malformed_info);
+
+ if (raw) {
+ // This stat only makes sense in raw mode.
+ pair1_add_sock_stat(
+ s, &s->stat_tx_malformed, &tx_malformed_info);
+ }
+
+ nni_stat_set_bool(&s->stat_raw, raw);
+ nni_stat_set_bool(&s->stat_poly, false);
+#endif
+
+ s->raw = raw;
+ s->uwq = nni_sock_sendq(sock);
+ s->urq = nni_sock_recvq(sock);
+ nni_atomic_init(&s->ttl);
+ nni_atomic_set(&s->ttl, 8);
+
+ return (0);
+}
+
+static int
+pair1_sock_init(void *arg, nni_sock *sock)
+{
+ return (pair1_sock_init_impl(arg, sock, false));
+}
+
+static int
+pair1_sock_init_raw(void *arg, nni_sock *sock)
+{
+ return (pair1_sock_init_impl(arg, sock, true));
+}
+
+static void
+pair1_pipe_stop(void *arg)
+{
+ pair1_pipe *p = arg;
+ pair1_sock *s = p->pair;
+
+ nni_mtx_lock(&s->mtx);
+ if (s->p == p) {
+ s->p = NULL;
+ }
+ nni_mtx_unlock(&s->mtx);
+ nni_aio_stop(&p->aio_send);
+ nni_aio_stop(&p->aio_recv);
+ nni_aio_stop(&p->aio_put);
+ nni_aio_stop(&p->aio_get);
+}
+
+static void
+pair1_pipe_fini(void *arg)
+{
+ pair1_pipe *p = arg;
+
+ nni_aio_fini(&p->aio_send);
+ nni_aio_fini(&p->aio_recv);
+ nni_aio_fini(&p->aio_put);
+ nni_aio_fini(&p->aio_get);
+}
+
+static int
+pair1_pipe_init(void *arg, nni_pipe *pipe, void *pair)
+{
+ pair1_pipe *p = arg;
+
+ nni_aio_init(&p->aio_send, pair1_pipe_send_cb, p);
+ nni_aio_init(&p->aio_recv, pair1_pipe_recv_cb, p);
+ nni_aio_init(&p->aio_get, pair1_pipe_get_cb, p);
+ nni_aio_init(&p->aio_put, pair1_pipe_put_cb, p);
+
+ p->pipe = pipe;
+ p->pair = pair;
+
+ return (0);
+}
+
+static int
+pair1_pipe_start(void *arg)
+{
+ pair1_pipe *p = arg;
+ pair1_sock *s = p->pair;
+
+ if (nni_pipe_peer(p->pipe) != NNG_PAIR1_PEER) {
+ BUMP_STAT(&s->stat_reject_mismatch);
+ // Peer protocol mismatch.
+ return (NNG_EPROTO);
+ }
+
+ nni_mtx_lock(&s->mtx);
+ if (s->p != NULL) {
+ nni_mtx_unlock(&s->mtx);
+ BUMP_STAT(&s->stat_reject_already);
+ return (NNG_EBUSY);
+ }
+ s->p = p;
+ nni_mtx_unlock(&s->mtx);
+
+ // Schedule a get.
+ nni_msgq_aio_get(s->uwq, &p->aio_get);
+
+ // And the pipe read of course.
+ nni_pipe_recv(p->pipe, &p->aio_recv);
+
+ return (0);
+}
+
+static void
+pair1_pipe_close(void *arg)
+{
+ pair1_pipe *p = arg;
+
+ nni_aio_close(&p->aio_send);
+ nni_aio_close(&p->aio_recv);
+ nni_aio_close(&p->aio_put);
+ nni_aio_close(&p->aio_get);
+}
+
+static void
+pair1_pipe_recv_cb(void *arg)
+{
+ pair1_pipe *p = arg;
+ pair1_sock *s = p->pair;
+ nni_msg * msg;
+ uint32_t hdr;
+ nni_pipe * pipe = p->pipe;
+ size_t len;
+
+ if (nni_aio_result(&p->aio_recv) != 0) {
+ nni_pipe_close(p->pipe);
+ return;
+ }
+
+ msg = nni_aio_get_msg(&p->aio_recv);
+ nni_aio_set_msg(&p->aio_recv, NULL);
+
+ // Store the pipe ID.
+ nni_msg_set_pipe(msg, nni_pipe_id(p->pipe));
+
+ // If the message is missing the hop count header, scrap it.
+ if ((nni_msg_len(msg) < sizeof(uint32_t)) ||
+ ((hdr = nni_msg_trim_u32(msg)) > 0xff)) {
+ BUMP_STAT(&s->stat_rx_malformed);
+ nni_msg_free(msg);
+ nni_pipe_close(pipe);
+ return;
+ }
+
+ len = nni_msg_len(msg);
+
+ // If we bounced too many times, discard the message, but
+ // keep getting more.
+ if ((int) hdr > nni_atomic_get(&s->ttl)) {
+ BUMP_STAT(&s->stat_ttl_drop);
+ nni_msg_free(msg);
+ nni_pipe_recv(pipe, &p->aio_recv);
+ return;
+ }
+
+ // Store the hop count in the header.
+ nni_msg_header_append_u32(msg, hdr);
+
+ // Send the message up.
+ nni_aio_set_msg(&p->aio_put, msg);
+ nni_sock_bump_rx(s->sock, len);
+ nni_msgq_aio_put(s->urq, &p->aio_put);
+}
+
+static void
+pair1_pipe_put_cb(void *arg)
+{
+ pair1_pipe *p = arg;
+
+ if (nni_aio_result(&p->aio_put) != 0) {
+ nni_msg_free(nni_aio_get_msg(&p->aio_put));
+ nni_aio_set_msg(&p->aio_put, NULL);
+ nni_pipe_close(p->pipe);
+ return;
+ }
+ nni_pipe_recv(p->pipe, &p->aio_recv);
+}
+
+static void
+pair1_pipe_get_cb(void *arg)
+{
+ pair1_pipe *p = arg;
+ pair1_sock *s = p->pair;
+ nni_msg * msg;
+ uint32_t hops;
+
+ if (nni_aio_result(&p->aio_get) != 0) {
+ nni_pipe_close(p->pipe);
+ return;
+ }
+
+ msg = nni_aio_get_msg(&p->aio_get);
+ nni_aio_set_msg(&p->aio_get, NULL);
+
+ // Raw mode messages have the header already formed, with a hop count.
+ // Cooked mode messages have no header so we have to add one.
+ if (s->raw) {
+ if ((nni_msg_header_len(msg) != sizeof(uint32_t)) ||
+ ((hops = nni_msg_header_trim_u32(msg)) > 254)) {
+ BUMP_STAT(&s->stat_tx_malformed);
+ nni_msg_free(msg);
+ nni_msgq_aio_get(s->uwq, &p->aio_get);
+ return;
+ }
+#if NNG_TEST_LIB
+ } else if (s->inject_header) {
+ nni_aio_set_msg(&p->aio_send, msg);
+ nni_pipe_send(p->pipe, &p->aio_send);
+ return;
+#endif
+ } else {
+ // Strip off any previously existing header, such as when
+ // replying to messages.
+ nni_msg_header_clear(msg);
+ hops = 0;
+ }
+
+ hops++;
+
+ // Insert the hops header.
+ nni_msg_header_append_u32(msg, hops);
+
+ nni_aio_set_msg(&p->aio_send, msg);
+ nni_pipe_send(p->pipe, &p->aio_send);
+}
+
+static void
+pair1_pipe_send_cb(void *arg)
+{
+ pair1_pipe *p = arg;
+ pair1_sock *s = p->pair;
+
+ if (nni_aio_result(&p->aio_send) != 0) {
+ nni_msg_free(nni_aio_get_msg(&p->aio_send));
+ nni_aio_set_msg(&p->aio_send, NULL);
+ nni_pipe_close(p->pipe);
+ return;
+ }
+
+ nni_msgq_aio_get(s->uwq, &p->aio_get);
+}
+
+static void
+pair1_sock_open(void *arg)
+{
+ NNI_ARG_UNUSED(arg);
+}
+
+static void
+pair1_sock_close(void *arg)
+{
+ NNI_ARG_UNUSED(arg);
+}
+
+static int
+pair1_sock_set_max_ttl(void *arg, const void *buf, size_t sz, nni_opt_type t)
+{
+ pair1_sock *s = arg;
+ int rv;
+ int ttl;
+
+ if ((rv = nni_copyin_int(&ttl, buf, sz, 1, NNI_MAX_MAX_TTL, t)) == 0) {
+ nni_atomic_set(&s->ttl, ttl);
+ }
+
+ return (rv);
+}
+
+static int
+pair1_sock_get_max_ttl(void *arg, void *buf, size_t *szp, nni_opt_type t)
+{
+ pair1_sock *s = arg;
+ return (nni_copyout_int(nni_atomic_get(&s->ttl), buf, szp, t));
+}
+
+#ifdef NNG_TEST_LIB
+static int
+pair1_set_test_inject_header(void *arg, const void *buf, size_t sz, nni_type t)
+{
+ pair1_sock *s = arg;
+ int rv;
+ nni_mtx_lock(&s->mtx);
+ rv = nni_copyin_bool(&s->inject_header, buf, sz, t);
+ nni_mtx_unlock(&s->mtx);
+ return (rv);
+}
+#endif
+
+static void
+pair1_sock_send(void *arg, nni_aio *aio)
+{
+ pair1_sock *s = arg;
+
+ nni_sock_bump_tx(s->sock, nni_msg_len(nni_aio_get_msg(aio)));
+ nni_msgq_aio_put(s->uwq, aio);
+}
+
+static void
+pair1_sock_recv(void *arg, nni_aio *aio)
+{
+ pair1_sock *s = arg;
+
+ nni_msgq_aio_get(s->urq, aio);
+}
+
+static nni_proto_pipe_ops pair1_pipe_ops = {
+ .pipe_size = sizeof(pair1_pipe),
+ .pipe_init = pair1_pipe_init,
+ .pipe_fini = pair1_pipe_fini,
+ .pipe_start = pair1_pipe_start,
+ .pipe_close = pair1_pipe_close,
+ .pipe_stop = pair1_pipe_stop,
+};
+
+static nni_option pair1_sock_options[] = {
+ {
+ .o_name = NNG_OPT_MAXTTL,
+ .o_get = pair1_sock_get_max_ttl,
+ .o_set = pair1_sock_set_max_ttl,
+ },
+#ifdef NNG_TEST_LIB
+ {
+ // Test only option to pass header unmolested. This allows
+ // us to inject bad header contents.
+ .o_name = "pair1_test_inject_header",
+ .o_set = pair1_set_test_inject_header,
+ },
+#endif
+ // terminate list
+ {
+ .o_name = NULL,
+ },
+};
+
+static nni_proto_sock_ops pair1_sock_ops = {
+ .sock_size = sizeof(pair1_sock),
+ .sock_init = pair1_sock_init,
+ .sock_fini = pair1_sock_fini,
+ .sock_open = pair1_sock_open,
+ .sock_close = pair1_sock_close,
+ .sock_recv = pair1_sock_recv,
+ .sock_send = pair1_sock_send,
+ .sock_options = pair1_sock_options,
+};
+
+static nni_proto pair1_proto = {
+ .proto_version = NNI_PROTOCOL_VERSION,
+ .proto_self = { NNG_PAIR1_SELF, NNG_PAIR1_SELF_NAME },
+ .proto_peer = { NNG_PAIR1_PEER, NNG_PAIR1_PEER_NAME },
+ .proto_flags = NNI_PROTO_FLAG_SNDRCV,
+ .proto_sock_ops = &pair1_sock_ops,
+ .proto_pipe_ops = &pair1_pipe_ops,
+};
+
+int
+nng_pair1_open(nng_socket *sock)
+{
+ return (nni_proto_open(sock, &pair1_proto));
+}
+
+static nni_proto_sock_ops pair1_sock_ops_raw = {
+ .sock_size = sizeof(pair1_sock),
+ .sock_init = pair1_sock_init_raw,
+ .sock_fini = pair1_sock_fini,
+ .sock_open = pair1_sock_open,
+ .sock_close = pair1_sock_close,
+ .sock_recv = pair1_sock_recv,
+ .sock_send = pair1_sock_send,
+ .sock_options = pair1_sock_options,
+};
+
+static nni_proto pair1_proto_raw = {
+ .proto_version = NNI_PROTOCOL_VERSION,
+ .proto_self = { NNG_PAIR1_SELF, NNG_PAIR1_SELF_NAME },
+ .proto_peer = { NNG_PAIR1_PEER, NNG_PAIR1_PEER_NAME },
+ .proto_flags = NNI_PROTO_FLAG_SNDRCV | NNI_PROTO_FLAG_RAW,
+ .proto_sock_ops = &pair1_sock_ops_raw,
+ .proto_pipe_ops = &pair1_pipe_ops,
+};
+
+int
+nng_pair1_open_raw(nng_socket *sock)
+{
+ return (nni_proto_open(sock, &pair1_proto_raw));
+}
diff --git a/src/sp/protocol/pair1/pair1_poly.c b/src/sp/protocol/pair1/pair1_poly.c
new file mode 100644
index 00000000..6c16745c
--- /dev/null
+++ b/src/sp/protocol/pair1/pair1_poly.c
@@ -0,0 +1,535 @@
+//
+// Copyright 2020 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2018 Capitar IT Group BV <info@capitar.com>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#include <stdlib.h>
+
+#include "core/nng_impl.h"
+#include "nng/protocol/pair1/pair.h"
+
+// Pair1 polyamorous mode. The PAIRv1 protocol is normally a simple 1:1
+// messaging pattern, but this mode offers the ability to use a best-effort
+// multicast type of communication. There are limitations however.
+// Most notably this does not interact well with nng_device type
+// proxies, and there is no support for raw mode.
+
+// THIS FEATURE IS DEPRECATED. We discourage use in new applications.
+
+#ifdef NNG_ENABLE_STATS
+#define BUMP_STAT(x) nni_stat_inc(x, 1)
+#else
+#define BUMP_STAT(x)
+#endif
+
+typedef struct pair1poly_pipe pair1poly_pipe;
+typedef struct pair1poly_sock pair1poly_sock;
+
+static void pair1poly_sock_get_cb(void *);
+static void pair1poly_pipe_send_cb(void *);
+static void pair1poly_pipe_recv_cb(void *);
+static void pair1poly_pipe_get_cb(void *);
+static void pair1poly_pipe_put_cb(void *);
+static void pair1poly_pipe_fini(void *);
+
+// pair1poly_sock is our per-socket protocol private structure.
+struct pair1poly_sock {
+ nni_msgq * uwq;
+ nni_msgq * urq;
+ nni_sock * sock;
+ nni_atomic_int ttl;
+ nni_mtx mtx;
+ nni_id_map pipes;
+ nni_list plist;
+ bool started;
+ nni_aio aio_get;
+ nni_stat_item stat_poly;
+ nni_stat_item stat_raw;
+ nni_stat_item stat_reject_mismatch;
+ nni_stat_item stat_reject_already;
+ nni_stat_item stat_ttl_drop;
+ nni_stat_item stat_rx_malformed;
+ nni_stat_item stat_tx_malformed;
+ nni_stat_item stat_tx_drop;
+};
+
+// pair1poly_pipe is our per-pipe protocol private structure.
+struct pair1poly_pipe {
+ nni_pipe * pipe;
+ pair1poly_sock *pair;
+ nni_msgq * send_queue;
+ nni_aio aio_send;
+ nni_aio aio_recv;
+ nni_aio aio_get;
+ nni_aio aio_put;
+ nni_list_node node;
+};
+
+static void
+pair1poly_sock_fini(void *arg)
+{
+ pair1poly_sock *s = arg;
+
+ nni_aio_fini(&s->aio_get);
+ nni_id_map_fini(&s->pipes);
+ nni_mtx_fini(&s->mtx);
+}
+
+#ifdef NNG_ENABLE_STATS
+static void
+pair1_add_sock_stat(
+ pair1poly_sock *s, nni_stat_item *item, const nni_stat_info *info)
+{
+ nni_stat_init(item, info);
+ nni_sock_add_stat(s->sock, item);
+}
+#endif
+
+static int
+pair1poly_sock_init(void *arg, nni_sock *sock)
+{
+ pair1poly_sock *s = arg;
+
+ nni_id_map_init(&s->pipes, 0, 0, false);
+ NNI_LIST_INIT(&s->plist, pair1poly_pipe, node);
+ s->sock = sock;
+
+ // Raw mode uses this.
+ nni_mtx_init(&s->mtx);
+
+ nni_aio_init(&s->aio_get, pair1poly_sock_get_cb, s);
+
+#ifdef NNG_ENABLE_STATS
+ static const nni_stat_info poly_info = {
+ .si_name = "poly",
+ .si_desc = "polyamorous mode?",
+ .si_type = NNG_STAT_BOOLEAN,
+ };
+ static const nni_stat_info raw_info = {
+ .si_name = "raw",
+ .si_desc = "raw mode?",
+ .si_type = NNG_STAT_BOOLEAN,
+ };
+ static const nni_stat_info mismatch_info = {
+ .si_name = "mismatch",
+ .si_desc = "pipes rejected (protocol mismatch)",
+ .si_type = NNG_STAT_COUNTER,
+ .si_atomic = true,
+ };
+ static const nni_stat_info already_info = {
+ .si_name = "already",
+ .si_desc = "pipes rejected (already connected)",
+ .si_type = NNG_STAT_COUNTER,
+ .si_atomic = true,
+ };
+ static const nni_stat_info ttl_drop_info = {
+ .si_name = "ttl_drop",
+ .si_desc = "messages dropped due to too many hops",
+ .si_type = NNG_STAT_COUNTER,
+ .si_unit = NNG_UNIT_MESSAGES,
+ .si_atomic = true,
+ };
+ static const nni_stat_info tx_drop_info = {
+ .si_name = "tx_drop",
+ .si_desc = "messages dropped undeliverable",
+ .si_type = NNG_STAT_COUNTER,
+ .si_unit = NNG_UNIT_MESSAGES,
+ .si_atomic = true,
+ };
+ static const nni_stat_info rx_malformed_info = {
+ .si_name = "rx_malformed",
+ .si_desc = "malformed messages received",
+ .si_type = NNG_STAT_COUNTER,
+ .si_unit = NNG_UNIT_MESSAGES,
+ .si_atomic = true,
+ };
+ static const nni_stat_info tx_malformed_info = {
+ .si_name = "tx_malformed",
+ .si_desc = "malformed messages not sent",
+ .si_type = NNG_STAT_COUNTER,
+ .si_unit = NNG_UNIT_MESSAGES,
+ .si_atomic = true,
+ };
+
+ pair1_add_sock_stat(s, &s->stat_poly, &poly_info);
+ pair1_add_sock_stat(s, &s->stat_raw, &raw_info);
+ pair1_add_sock_stat(s, &s->stat_reject_mismatch, &mismatch_info);
+ pair1_add_sock_stat(s, &s->stat_reject_already, &already_info);
+ pair1_add_sock_stat(s, &s->stat_ttl_drop, &ttl_drop_info);
+ pair1_add_sock_stat(s, &s->stat_tx_drop, &tx_drop_info);
+ pair1_add_sock_stat(s, &s->stat_rx_malformed, &rx_malformed_info);
+ pair1_add_sock_stat(s, &s->stat_tx_malformed, &tx_malformed_info);
+
+ nni_stat_set_bool(&s->stat_raw, false);
+ nni_stat_set_bool(&s->stat_poly, true);
+#endif
+
+ s->uwq = nni_sock_sendq(sock);
+ s->urq = nni_sock_recvq(sock);
+ nni_atomic_init(&s->ttl);
+ nni_atomic_set(&s->ttl, 8);
+
+ return (0);
+}
+
+static void
+pair1poly_pipe_stop(void *arg)
+{
+ pair1poly_pipe *p = arg;
+
+ nni_aio_stop(&p->aio_send);
+ nni_aio_stop(&p->aio_recv);
+ nni_aio_stop(&p->aio_put);
+ nni_aio_stop(&p->aio_get);
+}
+
+static void
+pair1poly_pipe_fini(void *arg)
+{
+ pair1poly_pipe *p = arg;
+
+ nni_aio_fini(&p->aio_send);
+ nni_aio_fini(&p->aio_recv);
+ nni_aio_fini(&p->aio_put);
+ nni_aio_fini(&p->aio_get);
+ nni_msgq_fini(p->send_queue);
+}
+
+static int
+pair1poly_pipe_init(void *arg, nni_pipe *pipe, void *pair)
+{
+ pair1poly_pipe *p = arg;
+ int rv;
+
+ nni_aio_init(&p->aio_send, pair1poly_pipe_send_cb, p);
+ nni_aio_init(&p->aio_recv, pair1poly_pipe_recv_cb, p);
+ nni_aio_init(&p->aio_get, pair1poly_pipe_get_cb, p);
+ nni_aio_init(&p->aio_put, pair1poly_pipe_put_cb, p);
+
+ if ((rv = nni_msgq_init(&p->send_queue, 2)) != 0) {
+ pair1poly_pipe_fini(p);
+ return (rv);
+ }
+
+ p->pipe = pipe;
+ p->pair = pair;
+
+ return (0);
+}
+
+static int
+pair1poly_pipe_start(void *arg)
+{
+ pair1poly_pipe *p = arg;
+ pair1poly_sock *s = p->pair;
+ uint32_t id;
+ int rv;
+
+ nni_mtx_lock(&s->mtx);
+ if (nni_pipe_peer(p->pipe) != NNG_PAIR1_PEER) {
+ nni_mtx_unlock(&s->mtx);
+ BUMP_STAT(&s->stat_reject_mismatch);
+ // Peer protocol mismatch.
+ return (NNG_EPROTO);
+ }
+
+ id = nni_pipe_id(p->pipe);
+ if ((rv = nni_id_set(&s->pipes, id, p)) != 0) {
+ nni_mtx_unlock(&s->mtx);
+ return (rv);
+ }
+ if (!s->started) {
+ nni_msgq_aio_get(s->uwq, &s->aio_get);
+ }
+ nni_list_append(&s->plist, p);
+ s->started = true;
+ nni_mtx_unlock(&s->mtx);
+
+ // Schedule a get. In polyamorous mode we get on the per pipe
+ // send_queue, as the socket distributes to us. In monogamous mode
+ // we bypass and get from the upper write queue directly (saving a
+ // set of context switches).
+ nni_msgq_aio_get(p->send_queue, &p->aio_get);
+
+ // And the pipe read of course.
+ nni_pipe_recv(p->pipe, &p->aio_recv);
+
+ return (0);
+}
+
+static void
+pair1poly_pipe_close(void *arg)
+{
+ pair1poly_pipe *p = arg;
+ pair1poly_sock *s = p->pair;
+
+ nni_aio_close(&p->aio_send);
+ nni_aio_close(&p->aio_recv);
+ nni_aio_close(&p->aio_put);
+ nni_aio_close(&p->aio_get);
+
+ nni_mtx_lock(&s->mtx);
+ nni_id_remove(&s->pipes, nni_pipe_id(p->pipe));
+ nni_list_node_remove(&p->node);
+ nni_mtx_unlock(&s->mtx);
+
+ nni_msgq_close(p->send_queue);
+}
+
+static void
+pair1poly_pipe_recv_cb(void *arg)
+{
+ pair1poly_pipe *p = arg;
+ pair1poly_sock *s = p->pair;
+ nni_msg * msg;
+ uint32_t hdr;
+ nni_pipe * pipe = p->pipe;
+ size_t len;
+
+ if (nni_aio_result(&p->aio_recv) != 0) {
+ nni_pipe_close(p->pipe);
+ return;
+ }
+
+ msg = nni_aio_get_msg(&p->aio_recv);
+ nni_aio_set_msg(&p->aio_recv, NULL);
+
+ // Store the pipe ID.
+ nni_msg_set_pipe(msg, nni_pipe_id(p->pipe));
+
+ // If the message is missing the hop count header, scrap it.
+ if ((nni_msg_len(msg) < sizeof(uint32_t)) ||
+ ((hdr = nni_msg_trim_u32(msg)) > 0xff)) {
+ BUMP_STAT(&s->stat_rx_malformed);
+ nni_msg_free(msg);
+ nni_pipe_close(pipe);
+ return;
+ }
+
+ len = nni_msg_len(msg);
+
+ // If we bounced too many times, discard the message, but
+ // keep getting more.
+ if ((int) hdr > nni_atomic_get(&s->ttl)) {
+ BUMP_STAT(&s->stat_ttl_drop);
+ nni_msg_free(msg);
+ nni_pipe_recv(pipe, &p->aio_recv);
+ return;
+ }
+
+ // Store the hop count in the header.
+ nni_msg_header_append_u32(msg, hdr);
+
+ // Send the message up.
+ nni_aio_set_msg(&p->aio_put, msg);
+ nni_sock_bump_rx(s->sock, len);
+ nni_msgq_aio_put(s->urq, &p->aio_put);
+}
+
+static void
+pair1poly_sock_get_cb(void *arg)
+{
+ pair1poly_pipe *p;
+ pair1poly_sock *s = arg;
+ nni_msg * msg;
+ uint32_t id;
+
+ if (nni_aio_result(&s->aio_get) != 0) {
+ // Socket closing...
+ return;
+ }
+
+ msg = nni_aio_get_msg(&s->aio_get);
+ nni_aio_set_msg(&s->aio_get, NULL);
+
+ p = NULL;
+ nni_mtx_lock(&s->mtx);
+ // If no pipe was requested, we look for any connected peer.
+ if (((id = nni_msg_get_pipe(msg)) == 0) &&
+ (!nni_list_empty(&s->plist))) {
+ p = nni_list_first(&s->plist);
+ } else {
+ p = nni_id_get(&s->pipes, id);
+ }
+
+ // Try a non-blocking send. If this fails we just discard the
+ // message. We have to do this to avoid head-of-line blocking
+ // for messages sent to other pipes. Note that there is some
+ // buffering in the send_queue.
+ if ((p == NULL) || nni_msgq_tryput(p->send_queue, msg) != 0) {
+ BUMP_STAT(&s->stat_tx_drop);
+ nni_msg_free(msg);
+ }
+
+ nni_mtx_unlock(&s->mtx);
+ nni_msgq_aio_get(s->uwq, &s->aio_get);
+}
+
+static void
+pair1poly_pipe_put_cb(void *arg)
+{
+ pair1poly_pipe *p = arg;
+
+ if (nni_aio_result(&p->aio_put) != 0) {
+ nni_msg_free(nni_aio_get_msg(&p->aio_put));
+ nni_aio_set_msg(&p->aio_put, NULL);
+ nni_pipe_close(p->pipe);
+ return;
+ }
+ nni_pipe_recv(p->pipe, &p->aio_recv);
+}
+
+static void
+pair1poly_pipe_get_cb(void *arg)
+{
+ pair1poly_pipe *p = arg;
+ nni_msg * msg;
+
+ if (nni_aio_result(&p->aio_get) != 0) {
+ nni_pipe_close(p->pipe);
+ return;
+ }
+
+ msg = nni_aio_get_msg(&p->aio_get);
+ nni_aio_set_msg(&p->aio_get, NULL);
+
+ // Cooked mode messages have no header so we have to add one.
+ // Strip off any previously existing header, such as when
+ // replying to messages.
+ nni_msg_header_clear(msg);
+
+ // Insert the hops header.
+ nni_msg_header_append_u32(msg, 1);
+
+ nni_aio_set_msg(&p->aio_send, msg);
+ nni_pipe_send(p->pipe, &p->aio_send);
+}
+
+static void
+pair1poly_pipe_send_cb(void *arg)
+{
+ pair1poly_pipe *p = arg;
+
+ if (nni_aio_result(&p->aio_send) != 0) {
+ nni_msg_free(nni_aio_get_msg(&p->aio_send));
+ nni_aio_set_msg(&p->aio_send, NULL);
+ nni_pipe_close(p->pipe);
+ return;
+ }
+
+ nni_msgq_aio_get(p->send_queue, &p->aio_get);
+}
+
+static void
+pair1poly_sock_open(void *arg)
+{
+ NNI_ARG_UNUSED(arg);
+}
+
+static void
+pair1poly_sock_close(void *arg)
+{
+ pair1poly_sock *s = arg;
+ nni_aio_close(&s->aio_get);
+}
+
+static int
+pair1poly_set_max_ttl(void *arg, const void *buf, size_t sz, nni_opt_type t)
+{
+ pair1poly_sock *s = arg;
+ int rv;
+ int ttl;
+
+ if ((rv = nni_copyin_int(&ttl, buf, sz, 1, NNI_MAX_MAX_TTL, t)) == 0) {
+ nni_atomic_set(&s->ttl, ttl);
+ }
+
+ return (rv);
+}
+
+static int
+pair1poly_get_max_ttl(void *arg, void *buf, size_t *szp, nni_opt_type t)
+{
+ pair1poly_sock *s = arg;
+ return (nni_copyout_int(nni_atomic_get(&s->ttl), buf, szp, t));
+}
+
+static int
+pair1poly_get_poly(void *arg, void *buf, size_t *szp, nni_opt_type t)
+{
+ NNI_ARG_UNUSED(arg);
+ return (nni_copyout_bool(true, buf, szp, t));
+}
+
+static void
+pair1poly_sock_send(void *arg, nni_aio *aio)
+{
+ pair1poly_sock *s = arg;
+
+ nni_sock_bump_tx(s->sock, nni_msg_len(nni_aio_get_msg(aio)));
+ nni_msgq_aio_put(s->uwq, aio);
+}
+
+static void
+pair1poly_sock_recv(void *arg, nni_aio *aio)
+{
+ pair1poly_sock *s = arg;
+
+ nni_msgq_aio_get(s->urq, aio);
+}
+
+static nni_proto_pipe_ops pair1poly_pipe_ops = {
+ .pipe_size = sizeof(pair1poly_pipe),
+ .pipe_init = pair1poly_pipe_init,
+ .pipe_fini = pair1poly_pipe_fini,
+ .pipe_start = pair1poly_pipe_start,
+ .pipe_close = pair1poly_pipe_close,
+ .pipe_stop = pair1poly_pipe_stop,
+};
+
+static nni_option pair1poly_sock_options[] = {
+ {
+ .o_name = NNG_OPT_MAXTTL,
+ .o_get = pair1poly_get_max_ttl,
+ .o_set = pair1poly_set_max_ttl,
+ },
+ {
+ .o_name = NNG_OPT_PAIR1_POLY,
+ .o_get = pair1poly_get_poly,
+ },
+ // terminate list
+ {
+ .o_name = NULL,
+ },
+};
+
+static nni_proto_sock_ops pair1poly_sock_ops = {
+ .sock_size = sizeof(pair1poly_sock),
+ .sock_init = pair1poly_sock_init,
+ .sock_fini = pair1poly_sock_fini,
+ .sock_open = pair1poly_sock_open,
+ .sock_close = pair1poly_sock_close,
+ .sock_recv = pair1poly_sock_recv,
+ .sock_send = pair1poly_sock_send,
+ .sock_options = pair1poly_sock_options,
+};
+
+static nni_proto pair1poly_proto = {
+ .proto_version = NNI_PROTOCOL_VERSION,
+ .proto_self = { NNG_PAIR1_SELF, NNG_PAIR1_SELF_NAME },
+ .proto_peer = { NNG_PAIR1_PEER, NNG_PAIR1_PEER_NAME },
+ .proto_flags = NNI_PROTO_FLAG_SNDRCV,
+ .proto_sock_ops = &pair1poly_sock_ops,
+ .proto_pipe_ops = &pair1poly_pipe_ops,
+};
+
+int
+nng_pair1_open_poly(nng_socket *sock)
+{
+ return (nni_proto_open(sock, &pair1poly_proto));
+}
diff --git a/src/sp/protocol/pair1/pair1_poly_test.c b/src/sp/protocol/pair1/pair1_poly_test.c
new file mode 100644
index 00000000..f26f7809
--- /dev/null
+++ b/src/sp/protocol/pair1/pair1_poly_test.c
@@ -0,0 +1,370 @@
+//
+// Copyright 2020 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2017 Capitar IT Group BV <info@capitar.com>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#include <nuts.h>
+
+#define SECOND 1000
+
+#define APPEND_STR(m, s) NUTS_PASS(nng_msg_append(m, s, strlen(s)))
+#define CHECK_STR(m, s) \
+ NUTS_TRUE(nng_msg_len(m) == strlen(s)); \
+ NUTS_TRUE(memcmp(nng_msg_body(m), s, strlen(s)) == 0)
+
+static void
+test_poly_identity(void)
+{
+ nng_socket s;
+ int p;
+ char * n;
+
+ NUTS_PASS(nng_pair1_open_poly(&s));
+ NUTS_PASS(nng_socket_get_int(s, NNG_OPT_PROTO, &p));
+ NUTS_TRUE(p == NUTS_PROTO(1u, 1u)); // 32
+ NUTS_PASS(nng_socket_get_int(s, NNG_OPT_PEER, &p));
+ NUTS_TRUE(p == NUTS_PROTO(1u, 1u)); // 33
+ NUTS_PASS(nng_socket_get_string(s, NNG_OPT_PROTONAME, &n));
+ NUTS_MATCH(n, "pair1");
+ nng_strfree(n);
+ NUTS_PASS(nng_socket_get_string(s, NNG_OPT_PEERNAME, &n));
+ NUTS_MATCH(n, "pair1");
+ nng_strfree(n);
+ NUTS_CLOSE(s);
+}
+
+void
+test_poly_best_effort(void)
+{
+ nng_socket s1;
+ nng_socket c1;
+ nng_msg * msg;
+
+ NUTS_PASS(nng_pair1_open_poly(&s1));
+ NUTS_PASS(nng_pair1_open(&c1));
+
+ NUTS_PASS(nng_setopt_int(s1, NNG_OPT_RECVBUF, 1));
+ NUTS_PASS(nng_setopt_int(s1, NNG_OPT_SENDBUF, 1));
+ NUTS_PASS(nng_setopt_int(c1, NNG_OPT_RECVBUF, 1));
+ NUTS_PASS(nng_setopt_ms(s1, NNG_OPT_SENDTIMEO, SECOND));
+
+ NUTS_MARRY(s1, c1);
+
+ for (int i = 0; i < 10; i++) {
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ NUTS_PASS(nng_sendmsg(s1, msg, 0));
+ }
+
+ NUTS_CLOSE(s1);
+ NUTS_CLOSE(c1);
+}
+
+void
+test_poly_cooked(void)
+{
+ nng_socket s1;
+ nng_socket c1;
+ nng_socket c2;
+ nng_msg * msg;
+ bool v;
+ nng_pipe p1;
+ nng_pipe p2;
+
+ NUTS_PASS(nng_pair1_open_poly(&s1));
+ NUTS_PASS(nng_pair1_open(&c1));
+ NUTS_PASS(nng_pair1_open(&c2));
+ NUTS_PASS(nng_setopt_ms(s1, NNG_OPT_SENDTIMEO, SECOND));
+ NUTS_PASS(nng_setopt_ms(c1, NNG_OPT_SENDTIMEO, SECOND));
+ NUTS_PASS(nng_setopt_ms(c2, NNG_OPT_SENDTIMEO, SECOND));
+ NUTS_PASS(nng_setopt_ms(s1, NNG_OPT_RECVTIMEO, SECOND / 10));
+ NUTS_PASS(nng_setopt_ms(c1, NNG_OPT_RECVTIMEO, SECOND / 10));
+ NUTS_PASS(nng_setopt_ms(c2, NNG_OPT_RECVTIMEO, SECOND / 10));
+
+ NUTS_PASS(nng_getopt_bool(s1, NNG_OPT_PAIR1_POLY, &v));
+ NUTS_TRUE(v);
+
+ NUTS_MARRY(s1, c1);
+ NUTS_MARRY(s1, c2);
+
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ APPEND_STR(msg, "ONE");
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_PASS(nng_recvmsg(s1, &msg, 0));
+ CHECK_STR(msg, "ONE");
+ p1 = nng_msg_get_pipe(msg);
+ NUTS_TRUE(nng_pipe_id(p1) > 0);
+ nng_msg_free(msg);
+
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ APPEND_STR(msg, "TWO");
+ NUTS_PASS(nng_sendmsg(c2, msg, 0));
+ NUTS_PASS(nng_recvmsg(s1, &msg, 0));
+ CHECK_STR(msg, "TWO");
+ p2 = nng_msg_get_pipe(msg);
+ NUTS_TRUE(nng_pipe_id(p2) > 0);
+ nng_msg_free(msg);
+
+ NUTS_TRUE(nng_pipe_id(p1) != nng_pipe_id(p2));
+
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+
+ nng_msg_set_pipe(msg, p1);
+ APPEND_STR(msg, "UNO");
+ NUTS_PASS(nng_sendmsg(s1, msg, 0));
+ NUTS_PASS(nng_recvmsg(c1, &msg, 0));
+ CHECK_STR(msg, "UNO");
+ nng_msg_free(msg);
+
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ nng_msg_set_pipe(msg, p2);
+ APPEND_STR(msg, "DOS");
+ NUTS_PASS(nng_sendmsg(s1, msg, 0));
+ NUTS_PASS(nng_recvmsg(c2, &msg, 0));
+ CHECK_STR(msg, "DOS");
+ nng_msg_free(msg);
+
+ NUTS_PASS(nng_close(c1));
+
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ nng_msg_set_pipe(msg, p1);
+ APPEND_STR(msg, "EIN");
+ NUTS_PASS(nng_sendmsg(s1, msg, 0));
+ NUTS_FAIL(nng_recvmsg(c2, &msg, 0), NNG_ETIMEDOUT);
+
+ NUTS_CLOSE(s1);
+ NUTS_CLOSE(c2);
+}
+
+void
+test_poly_default(void)
+{
+ nng_socket s1;
+ nng_socket c1;
+ nng_socket c2;
+ nng_msg * msg;
+
+ NUTS_PASS(nng_pair1_open_poly(&s1));
+ NUTS_PASS(nng_pair1_open(&c1));
+ NUTS_PASS(nng_pair1_open(&c2));
+ NUTS_PASS(nng_setopt_ms(s1, NNG_OPT_SENDTIMEO, SECOND));
+ NUTS_PASS(nng_setopt_ms(c1, NNG_OPT_SENDTIMEO, SECOND));
+ NUTS_PASS(nng_setopt_ms(c2, NNG_OPT_SENDTIMEO, SECOND));
+
+ NUTS_MARRY(s1, c1);
+ NUTS_MARRY(s1, c2);
+
+ // This assumes poly picks the first suitor. Applications
+ // should not make the same assumption.
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ APPEND_STR(msg, "YES");
+ NUTS_PASS(nng_sendmsg(s1, msg, 0));
+ NUTS_PASS(nng_recvmsg(c1, &msg, 0));
+ CHECK_STR(msg, "YES");
+ nng_msg_free(msg);
+
+ NUTS_CLOSE(c1);
+ NUTS_SLEEP(10);
+
+ // Verify that the other pipe is chosen as the next suitor.
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ APPEND_STR(msg, "AGAIN");
+ NUTS_PASS(nng_sendmsg(s1, msg, 0));
+ NUTS_PASS(nng_recvmsg(c2, &msg, 0));
+ CHECK_STR(msg, "AGAIN");
+ nng_msg_free(msg);
+
+ NUTS_CLOSE(s1);
+ NUTS_CLOSE(c2);
+}
+
+void
+test_poly_close_abort(void)
+{
+ nng_socket s;
+ nng_socket c;
+
+ NUTS_PASS(nng_pair1_open_poly(&s));
+ NUTS_PASS(nng_pair1_open(&c));
+ NUTS_PASS(nng_setopt_ms(s, NNG_OPT_RECVTIMEO, 100));
+ NUTS_PASS(nng_setopt_ms(s, NNG_OPT_SENDTIMEO, 200));
+ NUTS_PASS(nng_setopt_int(s, NNG_OPT_RECVBUF, 1));
+ NUTS_PASS(nng_setopt_int(c, NNG_OPT_SENDBUF, 20));
+
+ NUTS_MARRY(c, s);
+
+ for (int i = 0; i < 20; i++) {
+ NUTS_SEND(c, "TEST");
+ }
+ NUTS_SLEEP(50);
+
+ NUTS_CLOSE(s);
+ NUTS_CLOSE(c);
+}
+
+void
+test_poly_recv_no_header(void)
+{
+ nng_socket s;
+ nng_socket c;
+ nng_msg * m;
+
+ NUTS_PASS(nng_pair1_open_poly(&s));
+ NUTS_PASS(nng_pair1_open(&c));
+ NUTS_PASS(nng_setopt_bool(c, "pair1_test_inject_header", true));
+ NUTS_PASS(nng_setopt_ms(s, NNG_OPT_RECVTIMEO, 100));
+ NUTS_PASS(nng_setopt_ms(s, NNG_OPT_SENDTIMEO, 200));
+
+ NUTS_MARRY(c, s);
+
+ NUTS_PASS(nng_msg_alloc(&m, 0));
+ NUTS_PASS(nng_sendmsg(c, m, 0));
+ NUTS_FAIL(nng_recvmsg(s, &m, 0), NNG_ETIMEDOUT);
+
+ NUTS_CLOSE(c);
+ NUTS_CLOSE(s);
+}
+
+void
+test_poly_recv_garbage(void)
+{
+ nng_socket s;
+ nng_socket c;
+ nng_msg * m;
+
+ NUTS_PASS(nng_pair1_open_poly(&s));
+ NUTS_PASS(nng_pair1_open(&c));
+ NUTS_PASS(nng_setopt_bool(c, "pair1_test_inject_header", true));
+ NUTS_PASS(nng_setopt_ms(s, NNG_OPT_RECVTIMEO, 100));
+ NUTS_PASS(nng_setopt_ms(s, NNG_OPT_SENDTIMEO, 200));
+
+ NUTS_MARRY(c, s);
+
+ // ridiculous hop count
+ NUTS_PASS(nng_msg_alloc(&m, 0));
+ NUTS_PASS(nng_msg_append_u32(m, 0x1000));
+ NUTS_PASS(nng_sendmsg(c, m, 0));
+ NUTS_FAIL(nng_recvmsg(s, &m, 0), NNG_ETIMEDOUT);
+
+ NUTS_CLOSE(c);
+ NUTS_CLOSE(s);
+}
+
+void
+test_poly_ttl(void)
+{
+ nng_socket s1;
+ nng_socket c1;
+ nng_msg * msg;
+ uint32_t val;
+ int ttl;
+
+ NUTS_PASS(nng_pair1_open_poly(&s1));
+ NUTS_PASS(nng_pair1_open_raw(&c1));
+ NUTS_PASS(nng_setopt_ms(s1, NNG_OPT_RECVTIMEO, SECOND / 5));
+ NUTS_PASS(nng_setopt_ms(c1, NNG_OPT_RECVTIMEO, SECOND / 5));
+
+ // cannot set insane TTLs
+ NUTS_FAIL(nng_setopt_int(s1, NNG_OPT_MAXTTL, 0), NNG_EINVAL);
+ NUTS_FAIL(nng_setopt_int(s1, NNG_OPT_MAXTTL, 1000), NNG_EINVAL);
+ ttl = 8;
+ NUTS_FAIL(nng_setopt(s1, NNG_OPT_MAXTTL, &ttl, 1), NNG_EINVAL);
+ NUTS_FAIL(nng_setopt_bool(s1, NNG_OPT_MAXTTL, true), NNG_EBADTYPE);
+
+ NUTS_MARRY(s1, c1);
+
+ // Let's check enforcement of TTL
+ NUTS_PASS(nng_setopt_int(s1, NNG_OPT_MAXTTL, 4));
+ NUTS_PASS(nng_getopt_int(s1, NNG_OPT_MAXTTL, &ttl));
+ NUTS_TRUE(ttl == 4);
+
+ // Bad TTL bounces
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ NUTS_PASS(nng_msg_header_append_u32(msg, 4));
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_FAIL(nng_recvmsg(s1, &msg, 0), NNG_ETIMEDOUT);
+
+ // Good TTL passes
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ NUTS_PASS(nng_msg_append_u32(msg, 0xFEEDFACE));
+ NUTS_PASS(nng_msg_header_append_u32(msg, 3));
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_PASS(nng_recvmsg(s1, &msg, 0));
+ NUTS_PASS(nng_msg_trim_u32(msg, &val));
+ NUTS_TRUE(val == 0xFEEDFACE);
+ NUTS_PASS(nng_msg_header_trim_u32(msg, &val));
+ NUTS_TRUE(val == 4);
+ nng_msg_free(msg);
+
+ // Large TTL passes
+ NUTS_PASS(nng_setopt_int(s1, NNG_OPT_MAXTTL, 15));
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ NUTS_PASS(nng_msg_append_u32(msg, 1234));
+ NUTS_PASS(nng_msg_header_append_u32(msg, 14));
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_PASS(nng_recvmsg(s1, &msg, 0));
+ NUTS_PASS(nng_msg_trim_u32(msg, &val));
+ NUTS_TRUE(val == 1234);
+ NUTS_PASS(nng_msg_header_trim_u32(msg, &val));
+ NUTS_TRUE(val == 15);
+ nng_msg_free(msg);
+
+ // Max TTL fails
+ NUTS_PASS(nng_setopt_int(s1, NNG_OPT_MAXTTL, 15));
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ NUTS_PASS(nng_msg_header_append_u32(msg, 15));
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_FAIL(nng_recvmsg(s1, &msg, 0), NNG_ETIMEDOUT);
+
+ NUTS_CLOSE(s1);
+ NUTS_CLOSE(c1);
+}
+
+void
+test_poly_validate_peer(void)
+{
+ nng_socket s1, s2;
+ nng_stat * stats;
+ nng_stat * reject;
+ char * addr;
+
+ NUTS_ADDR(addr, "inproc");
+
+ NUTS_PASS(nng_pair1_open_poly(&s1));
+ NUTS_PASS(nng_pair0_open(&s2));
+
+ NUTS_PASS(nng_listen(s1, addr, NULL, 0));
+ NUTS_PASS(nng_dial(s2, addr, NULL, NNG_FLAG_NONBLOCK));
+
+ NUTS_SLEEP(100);
+ NUTS_PASS(nng_stats_get(&stats));
+
+ NUTS_TRUE(stats != NULL);
+ NUTS_TRUE((reject = nng_stat_find_socket(stats, s1)) != NULL);
+ NUTS_TRUE((reject = nng_stat_find(reject, "reject")) != NULL);
+
+ NUTS_TRUE(nng_stat_type(reject) == NNG_STAT_COUNTER);
+ NUTS_TRUE(nng_stat_value(reject) > 0);
+
+ NUTS_CLOSE(s1);
+ NUTS_CLOSE(s2);
+ nng_stats_free(stats);
+}
+
+TEST_LIST = {
+ { "pair1 poly identity", test_poly_identity },
+ { "pair1 poly best effort", test_poly_best_effort },
+ { "pair1 poly cooked", test_poly_cooked },
+ { "pair1 poly default", test_poly_default },
+ { "pair1 poly recv no header", test_poly_recv_no_header },
+ { "pair1 poly recv garbage", test_poly_recv_garbage },
+ { "pair1 poly ttl", test_poly_ttl },
+ { "pair1 poly close abort", test_poly_close_abort },
+ { "pair1 poly validate peer", test_poly_validate_peer },
+
+ { NULL, NULL },
+};
diff --git a/src/sp/protocol/pair1/pair1_test.c b/src/sp/protocol/pair1/pair1_test.c
new file mode 100644
index 00000000..881c4ac8
--- /dev/null
+++ b/src/sp/protocol/pair1/pair1_test.c
@@ -0,0 +1,433 @@
+//
+// Copyright 2020 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2017 Capitar IT Group BV <info@capitar.com>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#include <nuts.h>
+
+#define SECOND 1000
+
+#define APPEND_STR(m, s) NUTS_TRUE(nng_msg_append(m, s, strlen(s)) == 0)
+#define CHECK_STR(m, s) \
+ NUTS_TRUE(nng_msg_len(m) == strlen(s)); \
+ NUTS_TRUE(memcmp(nng_msg_body(m), s, strlen(s)) == 0)
+
+static void
+test_mono_identity(void)
+{
+ nng_socket s;
+ int p;
+ char * n;
+
+ NUTS_PASS(nng_pair1_open(&s));
+ NUTS_PASS(nng_socket_get_int(s, NNG_OPT_PROTO, &p));
+ NUTS_TRUE(p == NUTS_PROTO(1u, 1u)); // 32
+ NUTS_PASS(nng_socket_get_int(s, NNG_OPT_PEER, &p));
+ NUTS_TRUE(p == NUTS_PROTO(1u, 1u)); // 33
+ NUTS_PASS(nng_socket_get_string(s, NNG_OPT_PROTONAME, &n));
+ NUTS_MATCH(n, "pair1");
+ nng_strfree(n);
+ NUTS_PASS(nng_socket_get_string(s, NNG_OPT_PEERNAME, &n));
+ NUTS_MATCH(n, "pair1");
+ nng_strfree(n);
+ NUTS_CLOSE(s);
+}
+
+void
+test_mono_cooked(void)
+{
+ nng_socket s1;
+ nng_socket c1;
+ nng_msg * msg;
+
+ NUTS_PASS(nng_pair1_open(&s1));
+ NUTS_PASS(nng_pair1_open(&c1));
+ NUTS_PASS(nuts_marry(s1, c1));
+
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ NUTS_PASS(nng_msg_append(msg, "ALPHA", strlen("ALPHA") + 1));
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_PASS(nng_recvmsg(s1, &msg, 0));
+ NUTS_TRUE(nng_msg_len(msg) == strlen("ALPHA") + 1);
+ NUTS_MATCH(nng_msg_body(msg), "ALPHA");
+ nng_msg_free(msg);
+
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ NUTS_PASS(nng_msg_append(msg, "BETA", strlen("BETA") + 1));
+ NUTS_PASS(nng_sendmsg(s1, msg, 0));
+ NUTS_PASS(nng_recvmsg(c1, &msg, 0));
+ NUTS_TRUE(nng_msg_len(msg) == strlen("BETA") + 1);
+ NUTS_MATCH(nng_msg_body(msg), "BETA");
+
+ nng_msg_free(msg);
+ NUTS_CLOSE(c1);
+ NUTS_CLOSE(s1);
+}
+
+void
+test_mono_faithful(void)
+{
+ nng_socket s1;
+ nng_socket c1;
+ nng_socket c2;
+ nng_msg * msg;
+ const char *addr = "inproc://pair1_mono_faithful";
+
+ NUTS_PASS(nng_pair1_open(&s1));
+ NUTS_PASS(nng_pair1_open(&c1));
+ NUTS_PASS(nng_pair1_open(&c2));
+ NUTS_PASS(nng_setopt_ms(s1, NNG_OPT_RECVTIMEO, SECOND / 4));
+ NUTS_PASS(nng_setopt_ms(c1, NNG_OPT_SENDTIMEO, SECOND));
+ NUTS_PASS(nng_setopt_ms(c2, NNG_OPT_SENDTIMEO, SECOND));
+ NUTS_PASS(nng_setopt_int(c2, NNG_OPT_SENDBUF, 2));
+
+ NUTS_PASS(nng_listen(s1, addr, NULL, 0));
+ NUTS_MARRY(s1, c1);
+ NUTS_PASS(nng_dial(c2, addr, NULL, 0));
+
+ NUTS_SLEEP(100);
+
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ APPEND_STR(msg, "ONE");
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_PASS(nng_recvmsg(s1, &msg, 0));
+ CHECK_STR(msg, "ONE");
+ nng_msg_free(msg);
+
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ APPEND_STR(msg, "TWO");
+ NUTS_PASS(nng_sendmsg(c2, msg, 0));
+ NUTS_FAIL(nng_recvmsg(s1, &msg, 0), NNG_ETIMEDOUT);
+
+ NUTS_CLOSE(s1);
+ NUTS_CLOSE(c1);
+ NUTS_CLOSE(c2);
+}
+
+void
+test_mono_back_pressure(void)
+{
+ nng_socket s1;
+ nng_socket c1;
+ int i;
+ int rv;
+ nng_msg * msg;
+ nng_duration to = 100;
+
+ NUTS_PASS(nng_pair1_open(&s1));
+ NUTS_PASS(nng_pair1_open(&c1));
+ NUTS_PASS(nng_setopt_int(s1, NNG_OPT_RECVBUF, 1));
+ NUTS_PASS(nng_setopt_int(s1, NNG_OPT_SENDBUF, 1));
+ NUTS_PASS(nng_setopt_int(c1, NNG_OPT_RECVBUF, 1));
+ NUTS_PASS(nng_setopt_ms(s1, NNG_OPT_SENDTIMEO, to));
+
+ NUTS_MARRY(s1, c1);
+
+ // We choose to allow some buffering. In reality the
+ // buffer size is just 1, and we will fail after 2.
+ for (i = 0, rv = 0; i < 10; i++) {
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ if ((rv = nng_sendmsg(s1, msg, 0)) != 0) {
+ nng_msg_free(msg);
+ break;
+ }
+ }
+ NUTS_FAIL(rv, NNG_ETIMEDOUT);
+ NUTS_TRUE(i < 10);
+ NUTS_CLOSE(s1);
+ NUTS_CLOSE(c1);
+}
+
+void
+test_mono_raw_exchange(void)
+{
+ nng_socket s1;
+ nng_socket c1;
+
+ nng_msg *msg;
+ uint32_t hops;
+
+ NUTS_PASS(nng_pair1_open_raw(&s1));
+ NUTS_PASS(nng_pair1_open_raw(&c1));
+
+ NUTS_PASS(nng_setopt_ms(s1, NNG_OPT_RECVTIMEO, SECOND));
+ NUTS_PASS(nng_setopt_ms(c1, NNG_OPT_RECVTIMEO, SECOND));
+ NUTS_MARRY(s1, c1);
+
+ nng_pipe p = NNG_PIPE_INITIALIZER;
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ APPEND_STR(msg, "GAMMA");
+ NUTS_PASS(nng_msg_header_append_u32(msg, 1));
+ NUTS_TRUE(nng_msg_header_len(msg) == sizeof(uint32_t));
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_PASS(nng_recvmsg(s1, &msg, 0));
+ p = nng_msg_get_pipe(msg);
+ NUTS_TRUE(nng_pipe_id(p) > 0);
+
+ CHECK_STR(msg, "GAMMA");
+ NUTS_TRUE(nng_msg_header_len(msg) == sizeof(uint32_t));
+ NUTS_PASS(nng_msg_header_trim_u32(msg, &hops));
+ NUTS_TRUE(hops == 2);
+ nng_msg_free(msg);
+
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ APPEND_STR(msg, "EPSILON");
+ NUTS_PASS(nng_msg_header_append_u32(msg, 1));
+ NUTS_PASS(nng_sendmsg(s1, msg, 0));
+ NUTS_PASS(nng_recvmsg(c1, &msg, 0));
+ CHECK_STR(msg, "EPSILON");
+ NUTS_TRUE(nng_msg_header_len(msg) == sizeof(uint32_t));
+ NUTS_PASS(nng_msg_header_trim_u32(msg, &hops));
+ p = nng_msg_get_pipe(msg);
+ NUTS_TRUE(nng_pipe_id(p) > 0);
+
+ NUTS_TRUE(hops == 2);
+ nng_msg_free(msg);
+
+ NUTS_CLOSE(s1);
+ NUTS_CLOSE(c1);
+}
+
+void
+test_mono_raw_header(void)
+{
+ nng_socket s1;
+ nng_socket c1;
+ nng_msg * msg;
+ uint32_t v;
+
+ NUTS_PASS(nng_pair1_open_raw(&s1));
+ NUTS_PASS(nng_pair1_open_raw(&c1));
+
+ NUTS_PASS(nng_setopt_ms(s1, NNG_OPT_RECVTIMEO, SECOND / 5));
+ NUTS_PASS(nng_setopt_ms(c1, NNG_OPT_RECVTIMEO, SECOND / 5));
+ NUTS_MARRY(s1, c1);
+
+ // Missing bits in the header
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_FAIL(nng_recvmsg(s1, &msg, 0), NNG_ETIMEDOUT);
+
+ // Valid header works
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ NUTS_PASS(nng_msg_append_u32(msg, 0xFEEDFACE));
+ NUTS_PASS(nng_msg_header_append_u32(msg, 1));
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_PASS(nng_recvmsg(s1, &msg, 0));
+ NUTS_PASS(nng_msg_trim_u32(msg, &v));
+ NUTS_TRUE(v == 0xFEEDFACE);
+ nng_msg_free(msg);
+
+ // Header with reserved bits set dropped
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ NUTS_PASS(nng_msg_header_append_u32(msg, 0xDEAD0000));
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_FAIL(nng_recvmsg(s1, &msg, 0), NNG_ETIMEDOUT);
+
+ // Header with no chance to add another hop gets dropped
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ NUTS_PASS(nng_msg_header_append_u32(msg, 0xff));
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_FAIL(nng_recvmsg(s1, &msg, 0), NNG_ETIMEDOUT);
+
+ // With the same bits clear it works
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ NUTS_PASS(nng_msg_append_u32(msg, 0xFEEDFACE));
+ NUTS_PASS(nng_msg_header_append_u32(msg, 1));
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_PASS(nng_recvmsg(s1, &msg, 0));
+ NUTS_PASS(nng_msg_trim_u32(msg, &v));
+ NUTS_TRUE(v == 0xFEEDFACE);
+ nng_msg_free(msg);
+
+ NUTS_CLOSE(s1);
+ NUTS_CLOSE(c1);
+}
+
+void
+test_pair1_raw(void)
+{
+ nng_socket s1;
+ bool raw;
+
+ NUTS_PASS(nng_pair1_open(&s1));
+ NUTS_PASS(nng_getopt_bool(s1, NNG_OPT_RAW, &raw));
+ NUTS_TRUE(raw == false);
+ NUTS_FAIL(nng_setopt_bool(s1, NNG_OPT_RAW, true), NNG_EREADONLY);
+ NUTS_PASS(nng_close(s1));
+
+ NUTS_PASS(nng_pair1_open_raw(&s1));
+ NUTS_PASS(nng_getopt_bool(s1, NNG_OPT_RAW, &raw));
+ NUTS_TRUE(raw == true);
+ NUTS_FAIL(nng_setopt_bool(s1, NNG_OPT_RAW, false), NNG_EREADONLY);
+ NUTS_PASS(nng_close(s1));
+}
+
+void
+test_pair1_ttl(void)
+{
+ nng_socket s1;
+ nng_socket c1;
+ nng_msg * msg;
+ uint32_t val;
+ int ttl;
+
+ NUTS_PASS(nng_pair1_open_raw(&s1));
+ NUTS_PASS(nng_pair1_open_raw(&c1));
+ NUTS_PASS(nng_setopt_ms(s1, NNG_OPT_RECVTIMEO, SECOND / 5));
+ NUTS_PASS(nng_setopt_ms(c1, NNG_OPT_RECVTIMEO, SECOND / 5));
+
+ // cannot set insane TTLs
+ NUTS_FAIL(nng_setopt_int(s1, NNG_OPT_MAXTTL, 0), NNG_EINVAL);
+ NUTS_FAIL(nng_setopt_int(s1, NNG_OPT_MAXTTL, 1000), NNG_EINVAL);
+ ttl = 8;
+ NUTS_FAIL(nng_setopt(s1, NNG_OPT_MAXTTL, &ttl, 1), NNG_EINVAL);
+ NUTS_FAIL(nng_setopt_bool(s1, NNG_OPT_MAXTTL, true), NNG_EBADTYPE);
+
+ NUTS_MARRY(s1, c1);
+
+ // Let's check enforcement of TTL
+ NUTS_PASS(nng_setopt_int(s1, NNG_OPT_MAXTTL, 4));
+ NUTS_PASS(nng_getopt_int(s1, NNG_OPT_MAXTTL, &ttl));
+ NUTS_TRUE(ttl == 4);
+
+ // Bad TTL bounces
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ NUTS_PASS(nng_msg_header_append_u32(msg, 4));
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_FAIL(nng_recvmsg(s1, &msg, 0), NNG_ETIMEDOUT);
+
+ // Good TTL passes
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ NUTS_PASS(nng_msg_append_u32(msg, 0xFEEDFACE));
+ NUTS_PASS(nng_msg_header_append_u32(msg, 3));
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_PASS(nng_recvmsg(s1, &msg, 0));
+ NUTS_PASS(nng_msg_trim_u32(msg, &val));
+ NUTS_TRUE(val == 0xFEEDFACE);
+ NUTS_PASS(nng_msg_header_trim_u32(msg, &val));
+ NUTS_TRUE(val == 4);
+ nng_msg_free(msg);
+
+ // Large TTL passes
+ NUTS_PASS(nng_setopt_int(s1, NNG_OPT_MAXTTL, 15));
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ NUTS_PASS(nng_msg_append_u32(msg, 1234));
+ NUTS_PASS(nng_msg_header_append_u32(msg, 14));
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_PASS(nng_recvmsg(s1, &msg, 0));
+ NUTS_PASS(nng_msg_trim_u32(msg, &val));
+ NUTS_TRUE(val == 1234);
+ NUTS_PASS(nng_msg_header_trim_u32(msg, &val));
+ NUTS_TRUE(val == 15);
+ nng_msg_free(msg);
+
+ // Max TTL fails
+ NUTS_PASS(nng_setopt_int(s1, NNG_OPT_MAXTTL, 15));
+ NUTS_PASS(nng_msg_alloc(&msg, 0));
+ NUTS_PASS(nng_msg_header_append_u32(msg, 15));
+ NUTS_PASS(nng_sendmsg(c1, msg, 0));
+ NUTS_FAIL(nng_recvmsg(s1, &msg, 0), NNG_ETIMEDOUT);
+
+ NUTS_CLOSE(s1);
+ NUTS_CLOSE(c1);
+}
+
+void
+test_pair1_validate_peer(void)
+{
+ nng_socket s1, s2;
+ nng_stat * stats;
+ nng_stat * reject;
+ char * addr;
+
+ NUTS_ADDR(addr, "inproc");
+ NUTS_PASS(nng_pair1_open(&s1));
+ NUTS_PASS(nng_pair0_open(&s2));
+
+ NUTS_PASS(nng_listen(s1, addr, NULL, 0));
+ NUTS_PASS(nng_dial(s2, addr, NULL, NNG_FLAG_NONBLOCK));
+
+ NUTS_SLEEP(100);
+ NUTS_PASS(nng_stats_get(&stats));
+
+ NUTS_TRUE(stats != NULL);
+ NUTS_TRUE((reject = nng_stat_find_socket(stats, s1)) != NULL);
+ NUTS_TRUE((reject = nng_stat_find(reject, "reject")) != NULL);
+
+ NUTS_TRUE(nng_stat_type(reject) == NNG_STAT_COUNTER);
+ NUTS_TRUE(nng_stat_value(reject) > 0);
+
+ NUTS_CLOSE(s1);
+ NUTS_CLOSE(s2);
+ nng_stats_free(stats);
+}
+
+void
+test_pair1_recv_no_header(void)
+{
+ nng_socket s;
+ nng_socket c;
+ nng_msg * m;
+
+ NUTS_PASS(nng_pair1_open(&s));
+ NUTS_PASS(nng_pair1_open(&c));
+ NUTS_PASS(nng_setopt_bool(c, "pair1_test_inject_header", true));
+ NUTS_PASS(nng_setopt_ms(s, NNG_OPT_RECVTIMEO, 100));
+ NUTS_PASS(nng_setopt_ms(s, NNG_OPT_SENDTIMEO, 200));
+
+ NUTS_MARRY(c, s);
+
+ NUTS_PASS(nng_msg_alloc(&m, 0));
+ NUTS_PASS(nng_sendmsg(c, m, 0));
+ NUTS_FAIL(nng_recvmsg(s, &m, 0), NNG_ETIMEDOUT);
+
+ NUTS_CLOSE(c);
+ NUTS_CLOSE(s);
+}
+
+void
+test_pair1_recv_garbage(void)
+{
+ nng_socket s;
+ nng_socket c;
+ nng_msg * m;
+
+ NUTS_PASS(nng_pair1_open(&s));
+ NUTS_PASS(nng_pair1_open(&c));
+ NUTS_PASS(nng_setopt_bool(c, "pair1_test_inject_header", true));
+ NUTS_PASS(nng_setopt_ms(s, NNG_OPT_RECVTIMEO, 100));
+ NUTS_PASS(nng_setopt_ms(s, NNG_OPT_SENDTIMEO, 200));
+
+ NUTS_MARRY(c, s);
+
+ // ridiculous hop count
+ NUTS_PASS(nng_msg_alloc(&m, 0));
+ NUTS_PASS(nng_msg_append_u32(m, 0x1000));
+ NUTS_PASS(nng_sendmsg(c, m, 0));
+ NUTS_FAIL(nng_recvmsg(s, &m, 0), NNG_ETIMEDOUT);
+
+ NUTS_CLOSE(c);
+ NUTS_CLOSE(s);
+}
+
+NUTS_TESTS = {
+ { "pair1 mono identity", test_mono_identity },
+ { "pair1 mono cooked", test_mono_cooked },
+ { "pair1 mono faithful", test_mono_faithful },
+ { "pair1 mono back pressure", test_mono_back_pressure },
+ { "pair1 mono raw exchange", test_mono_raw_exchange },
+ { "pair1 mono raw header", test_mono_raw_header },
+ { "pair1 raw", test_pair1_raw },
+ { "pair1 ttl", test_pair1_ttl },
+ { "pair1 validate peer", test_pair1_validate_peer },
+ { "pair1 recv no header", test_pair1_recv_no_header },
+ { "pair1 recv garbage", test_pair1_recv_garbage },
+
+ { NULL, NULL },
+};