231 lines
4.8 KiB
C
231 lines
4.8 KiB
C
//
|
|
// Copyright 2023 Staysail Systems, Inc. <info@staysail.tech>
|
|
//
|
|
// 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 <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include <nng/nng.h>
|
|
|
|
#include "core/nng_impl.h"
|
|
#include "core/sockfd.h"
|
|
|
|
// We will accept up to this many FDs to be queued up for
|
|
// accept, before we start rejecting with NNG_ENOSPC. Once
|
|
// accept is performed, then another slot is available.
|
|
#define NNG_SFD_LISTEN_QUEUE 16
|
|
|
|
int
|
|
nni_sfd_dialer_alloc(nng_stream_dialer **dp, const nng_url *url)
|
|
{
|
|
NNI_ARG_UNUSED(dp);
|
|
NNI_ARG_UNUSED(url);
|
|
// No dialer support for this.
|
|
return (NNG_ENOTSUP);
|
|
}
|
|
|
|
typedef struct {
|
|
nng_stream_listener ops;
|
|
int listen_cnt; // how many FDs are waiting
|
|
int listen_q[NNG_SFD_LISTEN_QUEUE];
|
|
bool closed;
|
|
nni_list accept_q;
|
|
nni_mtx mtx;
|
|
} sfd_listener;
|
|
|
|
static void
|
|
sfd_listener_free(void *arg)
|
|
{
|
|
sfd_listener *l = arg;
|
|
nni_mtx_fini(&l->mtx);
|
|
NNI_FREE_STRUCT(l);
|
|
}
|
|
|
|
static void
|
|
sfd_listener_close(void *arg)
|
|
{
|
|
nni_aio *aio;
|
|
sfd_listener *l = arg;
|
|
nni_mtx_lock(&l->mtx);
|
|
l->closed = true;
|
|
while ((aio = nni_list_first(&l->accept_q)) != NULL) {
|
|
nni_aio_list_remove(aio);
|
|
nni_aio_finish_error(aio, NNG_ECLOSED);
|
|
}
|
|
for (int i = 0; i < l->listen_cnt; i++) {
|
|
nni_sfd_close_fd(l->listen_q[i]);
|
|
}
|
|
nni_mtx_unlock(&l->mtx);
|
|
}
|
|
|
|
static int
|
|
sfd_listener_listen(void *arg)
|
|
{
|
|
NNI_ARG_UNUSED(arg);
|
|
// nothing really for us to do
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
sfd_start_conn(sfd_listener *l, nni_aio *aio)
|
|
{
|
|
int fd;
|
|
int rv;
|
|
nni_sfd_conn *c;
|
|
NNI_ASSERT(l->listen_cnt > 0);
|
|
fd = l->listen_q[0];
|
|
for (int i = 1; i < l->listen_cnt; i++) {
|
|
l->listen_q[i] = l->listen_q[i + 1];
|
|
}
|
|
l->listen_cnt--;
|
|
if ((rv = nni_sfd_conn_alloc(&c, fd)) != 0) {
|
|
nni_aio_finish_error(aio, rv);
|
|
nni_sfd_close_fd(fd);
|
|
} else {
|
|
nni_aio_set_output(aio, 0, c);
|
|
nni_aio_finish(aio, 0, 0);
|
|
}
|
|
}
|
|
|
|
static void
|
|
sfd_cancel_accept(nni_aio *aio, void *arg, int rv)
|
|
{
|
|
sfd_listener *l = arg;
|
|
|
|
nni_mtx_lock(&l->mtx);
|
|
if (nni_aio_list_active(aio)) {
|
|
nni_aio_list_remove(aio);
|
|
nni_aio_finish_error(aio, rv);
|
|
}
|
|
nni_mtx_unlock(&l->mtx);
|
|
}
|
|
|
|
static void
|
|
sfd_listener_accept(void *arg, nng_aio *aio)
|
|
{
|
|
sfd_listener *l = arg;
|
|
|
|
if (nni_aio_begin(aio) != 0) {
|
|
return;
|
|
}
|
|
nni_mtx_lock(&l->mtx);
|
|
if (l->closed) {
|
|
nni_mtx_unlock(&l->mtx);
|
|
nni_aio_finish_error(aio, NNG_ECLOSED);
|
|
return;
|
|
}
|
|
|
|
if (l->listen_cnt) {
|
|
sfd_start_conn(l, aio);
|
|
} else {
|
|
nni_aio_schedule(aio, sfd_cancel_accept, l);
|
|
nni_aio_list_append(&l->accept_q, aio);
|
|
}
|
|
nni_mtx_unlock(&l->mtx);
|
|
}
|
|
|
|
static int
|
|
sfd_listener_set_fd(void *arg, const void *buf, size_t sz, nni_type t)
|
|
{
|
|
sfd_listener *l = arg;
|
|
nni_aio *aio;
|
|
int fd;
|
|
int rv;
|
|
|
|
if ((rv = nni_copyin_int(&fd, buf, sz, NNI_MININT, NNI_MAXINT, t)) !=
|
|
0) {
|
|
return (rv);
|
|
}
|
|
|
|
nni_mtx_lock(&l->mtx);
|
|
if (l->closed) {
|
|
nni_mtx_unlock(&l->mtx);
|
|
return (NNG_ECLOSED);
|
|
}
|
|
|
|
if (l->listen_cnt == NNG_SFD_LISTEN_QUEUE) {
|
|
nni_mtx_unlock(&l->mtx);
|
|
return (NNG_ENOSPC);
|
|
}
|
|
|
|
l->listen_q[l->listen_cnt++] = fd;
|
|
|
|
// if someone was waiting in accept, give it to them now
|
|
if ((aio = nni_list_first(&l->accept_q)) != NULL) {
|
|
nni_aio_list_remove(aio);
|
|
sfd_start_conn(l, aio);
|
|
}
|
|
|
|
nni_mtx_unlock(&l->mtx);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
sfd_listener_get_addr(void *arg, void *buf, size_t *szp, nni_type t)
|
|
{
|
|
NNI_ARG_UNUSED(arg);
|
|
nng_sockaddr sa;
|
|
sa.s_family = NNG_AF_UNSPEC;
|
|
return (nni_copyout_sockaddr(&sa, buf, szp, t));
|
|
}
|
|
|
|
static const nni_option sfd_listener_options[] = {
|
|
{
|
|
.o_name = NNG_OPT_SOCKET_FD,
|
|
.o_set = sfd_listener_set_fd,
|
|
},
|
|
{
|
|
.o_name = NNG_OPT_LOCADDR,
|
|
.o_get = sfd_listener_get_addr,
|
|
},
|
|
{
|
|
.o_name = NULL,
|
|
},
|
|
};
|
|
|
|
static int
|
|
sfd_listener_get(
|
|
void *arg, const char *name, void *buf, size_t *szp, nni_type t)
|
|
{
|
|
sfd_listener *l = arg;
|
|
return (nni_getopt(sfd_listener_options, name, l, buf, szp, t));
|
|
}
|
|
|
|
static int
|
|
sfd_listener_set(
|
|
void *arg, const char *name, const void *buf, size_t sz, nni_type t)
|
|
{
|
|
sfd_listener *l = arg;
|
|
return (nni_setopt(sfd_listener_options, name, l, buf, sz, t));
|
|
}
|
|
|
|
int
|
|
nni_sfd_listener_alloc(nng_stream_listener **lp, const nng_url *url)
|
|
{
|
|
sfd_listener *l;
|
|
|
|
NNI_ARG_UNUSED(url);
|
|
|
|
if ((l = NNI_ALLOC_STRUCT(l)) == NULL) {
|
|
return (NNG_ENOMEM);
|
|
}
|
|
memset(l->listen_q, 0, sizeof(l->listen_q));
|
|
l->listen_cnt = 0;
|
|
nni_aio_list_init(&l->accept_q);
|
|
nni_mtx_init(&l->mtx);
|
|
|
|
l->ops.sl_free = sfd_listener_free;
|
|
l->ops.sl_close = sfd_listener_close;
|
|
l->ops.sl_listen = sfd_listener_listen;
|
|
l->ops.sl_accept = sfd_listener_accept;
|
|
l->ops.sl_get = sfd_listener_get;
|
|
l->ops.sl_set = sfd_listener_set;
|
|
|
|
*lp = (void *) l;
|
|
return (0);
|
|
}
|