245 lines
5.4 KiB
C
245 lines
5.4 KiB
C
|
//
|
||
|
// Copyright 2021 Staysail Systems, Inc. <info@staysail.com>
|
||
|
// 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 "core/nng_impl.h"
|
||
|
|
||
|
typedef struct device_data_s device_data;
|
||
|
typedef struct device_path_s device_path;
|
||
|
|
||
|
struct device_path_s {
|
||
|
int state;
|
||
|
device_data *d;
|
||
|
nni_sock *src;
|
||
|
nni_sock *dst;
|
||
|
nni_aio aio;
|
||
|
};
|
||
|
|
||
|
#define NNI_DEVICE_STATE_INIT 0
|
||
|
#define NNI_DEVICE_STATE_RECV 1
|
||
|
#define NNI_DEVICE_STATE_SEND 2
|
||
|
#define NNI_DEVICE_STATE_FINI 3
|
||
|
|
||
|
struct device_data_s {
|
||
|
nni_aio *user;
|
||
|
int num_paths;
|
||
|
int running;
|
||
|
int rv;
|
||
|
device_path paths[2];
|
||
|
nni_reap_node reap;
|
||
|
};
|
||
|
|
||
|
static void device_fini(void *);
|
||
|
|
||
|
static nni_mtx device_mtx = NNI_MTX_INITIALIZER;
|
||
|
static nni_reap_list device_reap = {
|
||
|
.rl_offset = offsetof(device_data, reap),
|
||
|
.rl_func = device_fini,
|
||
|
};
|
||
|
|
||
|
static void
|
||
|
device_fini(void *arg)
|
||
|
{
|
||
|
device_data *d = arg;
|
||
|
|
||
|
for (int i = 0; i < d->num_paths; i++) {
|
||
|
nni_aio_stop(&d->paths[i].aio);
|
||
|
}
|
||
|
NNI_FREE_STRUCT(d);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
device_cancel(nni_aio *aio, void *arg, int rv)
|
||
|
{
|
||
|
device_data *d = arg;
|
||
|
// cancellation is the only path to shutting it down.
|
||
|
|
||
|
nni_mtx_lock(&device_mtx);
|
||
|
if (d->user == aio) {
|
||
|
for (int i = 0; i < d->num_paths; i++) {
|
||
|
nni_aio_abort(&d->paths[i].aio, rv);
|
||
|
}
|
||
|
}
|
||
|
nni_mtx_unlock(&device_mtx);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
device_cb(void *arg)
|
||
|
{
|
||
|
device_path *p = arg;
|
||
|
device_data *d = p->d;
|
||
|
int rv;
|
||
|
|
||
|
if ((rv = nni_aio_result(&p->aio)) != 0) {
|
||
|
nni_mtx_lock(&device_mtx);
|
||
|
if (p->state == NNI_DEVICE_STATE_SEND) {
|
||
|
nni_msg_free(nni_aio_get_msg(&p->aio));
|
||
|
nni_aio_set_msg(&p->aio, NULL);
|
||
|
}
|
||
|
p->state = NNI_DEVICE_STATE_FINI;
|
||
|
d->running--;
|
||
|
if (d->rv == 0) {
|
||
|
d->rv = rv;
|
||
|
}
|
||
|
for (int i = 0; i < d->num_paths; i++) {
|
||
|
if (p != &d->paths[i]) {
|
||
|
nni_aio_abort(&d->paths[i].aio, rv);
|
||
|
}
|
||
|
}
|
||
|
if (d->running == 0) {
|
||
|
if (d->user != NULL) {
|
||
|
nni_aio_finish_error(d->user, d->rv);
|
||
|
d->user = NULL;
|
||
|
}
|
||
|
nni_sock_rele(d->paths[0].src);
|
||
|
nni_sock_rele(d->paths[0].dst);
|
||
|
|
||
|
nni_reap(&device_reap, d);
|
||
|
}
|
||
|
nni_mtx_unlock(&device_mtx);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
switch (p->state) {
|
||
|
case NNI_DEVICE_STATE_INIT:
|
||
|
break;
|
||
|
case NNI_DEVICE_STATE_SEND:
|
||
|
p->state = NNI_DEVICE_STATE_RECV;
|
||
|
nni_sock_recv(p->src, &p->aio);
|
||
|
break;
|
||
|
case NNI_DEVICE_STATE_RECV:
|
||
|
// Leave the message where it is.
|
||
|
p->state = NNI_DEVICE_STATE_SEND;
|
||
|
nni_sock_send(p->dst, &p->aio);
|
||
|
break;
|
||
|
case NNI_DEVICE_STATE_FINI:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
device_init(device_data **dp, nni_sock *s1, nni_sock *s2)
|
||
|
{
|
||
|
int num_paths = 2;
|
||
|
int i;
|
||
|
bool raw;
|
||
|
size_t rsz;
|
||
|
device_data *d;
|
||
|
|
||
|
// Specifying either of these as null turns the device into
|
||
|
// a reflector.
|
||
|
if (s1 == NULL) {
|
||
|
s1 = s2;
|
||
|
}
|
||
|
if (s2 == NULL) {
|
||
|
s2 = s1;
|
||
|
}
|
||
|
// At least one of the sockets must be valid.
|
||
|
if ((s1 == NULL) || (s2 == NULL)) {
|
||
|
return (NNG_EINVAL);
|
||
|
}
|
||
|
if ((nni_sock_peer_id(s1) != nni_sock_proto_id(s2)) ||
|
||
|
(nni_sock_peer_id(s2) != nni_sock_proto_id(s1))) {
|
||
|
return (NNG_EINVAL);
|
||
|
}
|
||
|
|
||
|
raw = false;
|
||
|
rsz = sizeof(raw);
|
||
|
if (((nni_sock_getopt(s1, NNG_OPT_RAW, &raw, &rsz, NNI_TYPE_BOOL) !=
|
||
|
0)) ||
|
||
|
(!raw)) {
|
||
|
return (NNG_EINVAL);
|
||
|
}
|
||
|
|
||
|
rsz = sizeof(raw);
|
||
|
if (((nni_sock_getopt(s2, NNG_OPT_RAW, &raw, &rsz, NNI_TYPE_BOOL) !=
|
||
|
0)) ||
|
||
|
(!raw)) {
|
||
|
return (NNG_EINVAL);
|
||
|
}
|
||
|
|
||
|
// Note we assume that since they are peers, we only need to look
|
||
|
// at the recv flags -- the other side is assumed to be able
|
||
|
// to send.
|
||
|
if ((nni_sock_flags(s1) & NNI_PROTO_FLAG_RCV) == 0) {
|
||
|
nni_sock *temp = s1;
|
||
|
s1 = s2;
|
||
|
s2 = temp;
|
||
|
}
|
||
|
|
||
|
NNI_ASSERT((nni_sock_flags(s1) & NNI_PROTO_FLAG_RCV) != 0);
|
||
|
|
||
|
// Only run one forwarder if the protocols are not bidirectional, or
|
||
|
// if the source and destination sockets are identical. (The latter is
|
||
|
// not strictly necessary, but it saves resources and minimizes any
|
||
|
// extra reordering.)
|
||
|
if (((nni_sock_flags(s2) & NNI_PROTO_FLAG_RCV) == 0) || (s1 == s2)) {
|
||
|
num_paths = 1;
|
||
|
}
|
||
|
|
||
|
if ((d = NNI_ALLOC_STRUCT(d)) == NULL) {
|
||
|
return (NNG_ENOMEM);
|
||
|
}
|
||
|
|
||
|
d->num_paths = 0;
|
||
|
for (i = 0; i < num_paths; i++) {
|
||
|
device_path *p = &d->paths[i];
|
||
|
p->src = i == 0 ? s1 : s2;
|
||
|
p->dst = i == 0 ? s2 : s1;
|
||
|
p->d = d;
|
||
|
p->state = NNI_DEVICE_STATE_INIT;
|
||
|
|
||
|
nni_aio_init(&p->aio, device_cb, p);
|
||
|
|
||
|
nni_aio_set_timeout(&p->aio, NNG_DURATION_INFINITE);
|
||
|
}
|
||
|
nni_sock_hold(d->paths[0].src);
|
||
|
nni_sock_hold(d->paths[0].dst);
|
||
|
|
||
|
d->num_paths = num_paths;
|
||
|
*dp = d;
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
device_start(device_data *d, nni_aio *user)
|
||
|
{
|
||
|
d->user = user;
|
||
|
for (int i = 0; i < d->num_paths; i++) {
|
||
|
device_path *p = &d->paths[i];
|
||
|
p->state = NNI_DEVICE_STATE_RECV;
|
||
|
nni_sock_recv(p->src, &p->aio);
|
||
|
d->running++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nni_device(nni_aio *aio, nni_sock *s1, nni_sock *s2)
|
||
|
{
|
||
|
device_data *d;
|
||
|
int rv;
|
||
|
|
||
|
if (nni_aio_begin(aio) != 0) {
|
||
|
return;
|
||
|
}
|
||
|
nni_mtx_lock(&device_mtx);
|
||
|
if ((rv = device_init(&d, s1, s2)) != 0) {
|
||
|
nni_mtx_unlock(&device_mtx);
|
||
|
nni_aio_finish_error(aio, rv);
|
||
|
return;
|
||
|
}
|
||
|
if ((rv = nni_aio_schedule(aio, device_cancel, d)) != 0) {
|
||
|
nni_mtx_unlock(&device_mtx);
|
||
|
nni_aio_finish_error(aio, rv);
|
||
|
nni_reap(&device_reap, d);
|
||
|
}
|
||
|
device_start(d, aio);
|
||
|
nni_mtx_unlock(&device_mtx);
|
||
|
}
|