#include "ssl_iot_common.h"
#include "sllist.h"
#include "sllist-inl.h"
typedef struct {
void *parent;
char buf[1];
} my_WBUF;
typedef struct {
sllist_node slnode;
lcb_ioC_write2_callback cb;
void *uarg;
void *iovroot_;
lcb_IOV *iov;
lcb_size_t niov;
} my_WCTX;
typedef struct {
IOTSSL_COMMON_FIELDS
lcb_sockdata_t *sd;
lcbio_pTIMER as_read;
lcbio_pTIMER as_write;
lcb_IOV urd_iov;
void *urd_arg;
my_WCTX *wctx_cached;
lcb_ioC_read2_callback urd_cb;
sllist_root writes;
int rdactive;
int closed;
int entered;
} lcbio_CSSL;
#define CS_FROM_IOPS(iops) (lcbio_CSSL *)IOTSSL_FROM_IOPS(iops)
#define SCHEDULE_WANT_SAFE(cs) if (!(cs)->entered) { schedule_wants(cs); }
static void appdata_encode(lcbio_CSSL *);
static void appdata_free_flushed(lcbio_CSSL *);
static void appdata_read(lcbio_CSSL *);
static void schedule_wants(lcbio_CSSL *cs);
static int maybe_set_error(lcbio_CSSL *cs, int rv)
{
return iotssl_maybe_error((lcbio_XSSL *)cs, rv);
}
static void
appdata_free_flushed(lcbio_CSSL *cs)
{
sllist_iterator iter;
SLLIST_ITERFOR(&cs->writes, &iter) {
my_WCTX *cur = SLLIST_ITEM(iter.cur, my_WCTX, slnode);
if (cur->niov && cs->error == 0) {
break;
}
cur->cb(cs->sd, cs->error?-1:0, cur->uarg);
sllist_iter_remove(&cs->writes, &iter);
free(cur->iovroot_);
if (cs->wctx_cached) {
free(cur);
} else {
cs->wctx_cached = cur;
}
}
}
static void
appdata_encode(lcbio_CSSL *cs)
{
sllist_node *cur;
SLLIST_FOREACH(&cs->writes, cur) {
my_WCTX *ctx = SLLIST_ITEM(cur, my_WCTX, slnode);
for (; ctx->niov && cs->error == 0; ctx->niov--, ctx->iov++) {
int rv;
assert(ctx->iov->iov_len);
rv = SSL_write(cs->ssl, ctx->iov->iov_base, ctx->iov->iov_len);
if (rv > 0) {
continue;
} else if (maybe_set_error(cs, rv) == 0) {
SCHEDULE_WANT_SAFE(cs)
return;
} else {
IOTSSL_ERRNO(cs) = EINVAL;
}
}
}
}
static void
async_write(void *arg)
{
lcbio_CSSL *cs = arg;
appdata_encode(cs);
appdata_free_flushed(cs);
}
static void
write_callback(lcb_sockdata_t *sd, int status, void *arg)
{
my_WBUF *wb = arg;
lcbio_CSSL *cs = wb->parent;
if (status) {
IOTSSL_ERRNO(cs) = IOT_ERRNO(cs->orig);
cs->error = 1;
}
free(wb);
appdata_free_flushed(cs);
lcbio_table_unref(&cs->base_);
(void) sd;
}
static void
appdata_read(lcbio_CSSL *cs)
{
int nr;
lcb_ioC_read2_callback cb = cs->urd_cb;
if (!cb) {
return;
}
assert(!cs->rdactive);
nr = SSL_read(cs->ssl, cs->urd_iov.iov_base, cs->urd_iov.iov_len);
if (nr > 0) {
} else if (cs->closed || nr == 0) {
nr = 0;
} else if (maybe_set_error(cs, nr) == 0) {
return;
}
cs->urd_cb = NULL;
cb(cs->sd, nr, cs->urd_arg);
}
static void
read_callback(lcb_sockdata_t *sd, lcb_ssize_t nr, void *arg)
{
lcbio_CSSL *cs = arg;
cs->rdactive = 0;
cs->entered++;
if (nr > 0) {
BUF_MEM *mb;
BIO_clear_retry_flags(cs->rbio);
BIO_get_mem_ptr(cs->rbio, &mb);
mb->length += nr;
} else if (nr == 0) {
cs->closed = 1;
cs->error = 1;
} else {
cs->error = 1;
IOTSSL_ERRNO(cs) = IOT_ERRNO(cs->orig);
}
appdata_encode(cs);
appdata_read(cs);
cs->entered--;
schedule_wants(cs);
lcbio_table_unref(&cs->base_);
(void) sd;
}
static void
schedule_wants(lcbio_CSSL *cs)
{
size_t npend = BIO_ctrl_pending(cs->wbio);
char dummy;
int has_appdata = 0;
if (SSL_peek(cs->ssl, &dummy, 1) == 1) {
has_appdata = 1;
}
if (npend) {
my_WBUF *wb = malloc(sizeof(*wb) + npend);
lcb_IOV iov;
BIO_read(cs->wbio, wb->buf, npend);
iov.iov_base = wb->buf;
iov.iov_len = npend;
wb->parent = cs;
lcbio_table_ref(&cs->base_);
IOT_V1(cs->orig).write2(
IOT_ARG(cs->orig), cs->sd, &iov, 1, wb, write_callback);
}
if (cs->rdactive == 0) {
if (cs->error) {
lcbio_async_signal(cs->as_read);
} else if (SSL_want_read(cs->ssl) || (cs->urd_cb && has_appdata == 0)) {
BUF_MEM *mb;
lcb_IOV iov;
cs->rdactive = 1;
BIO_get_mem_ptr(cs->rbio, &mb);
iotssl_bm_reserve(mb);
iov.iov_base = mb->data + mb->length;
iov.iov_len = mb->max - mb->length;
lcbio_table_ref(&cs->base_);
IOT_V1(cs->orig).read2(
IOT_ARG(cs->orig), cs->sd, &iov, 1, cs, read_callback);
}
}
}
static int
Cssl_read2(lcb_io_opt_t iops, lcb_sockdata_t *sd, lcb_IOV *iov, lcb_size_t niov,
void *uarg, lcb_ioC_read2_callback callback)
{
lcbio_CSSL *cs = CS_FROM_IOPS(iops);
cs->urd_iov = *iov;
cs->urd_arg = uarg;
cs->urd_cb = callback;
IOTSSL_PENDING_PRECHECK(cs->ssl);
if (IOTSSL_IS_PENDING(cs->ssl)) {
lcbio_async_signal(cs->as_read);
} else {
SCHEDULE_WANT_SAFE(cs);
}
(void) niov; (void) sd;
return 0;
}
static int
Cssl_write2(lcb_io_opt_t io, lcb_sockdata_t *sd, lcb_IOV *iov, lcb_size_t niov,
void *uarg, lcb_ioC_write2_callback callback)
{
lcbio_CSSL *cs = CS_FROM_IOPS(io);
my_WCTX *wc;
if (cs->wctx_cached) {
wc = cs->wctx_cached;
cs->wctx_cached = NULL;
memset(wc, 0, sizeof *wc);
} else {
wc = calloc(1, sizeof(*wc));
}
wc->uarg = uarg;
wc->cb = callback;
if (cs->error == 0 && SLLIST_IS_EMPTY(&cs->writes)) {
unsigned ii;
for (ii = 0; ii < niov; ++ii) {
int rv = SSL_write(cs->ssl, iov->iov_base, iov->iov_len);
if (rv > 0) {
iov++;
niov--;
} else {
maybe_set_error(cs, rv);
break;
}
}
}
sllist_append(&cs->writes, &wc->slnode);
if (niov && cs->error == 0) {
wc->niov = niov;
wc->iov = malloc(sizeof (*iov) * wc->niov);
wc->iovroot_ = wc->iov;
memcpy(wc->iov, iov, sizeof (*iov) * niov);
appdata_encode(cs);
}
lcbio_async_signal(cs->as_write);
(void) sd;
return 0;
}
static unsigned
Cssl_close(lcb_io_opt_t io, lcb_sockdata_t *sd)
{
lcbio_CSSL *cs = CS_FROM_IOPS(io);
IOT_V1(cs->orig).close(IOT_ARG(cs->orig), sd);
cs->error = 1;
if (!SLLIST_IS_EMPTY(&cs->writes)) {
lcbio_async_signal(cs->as_write);
}
return 0;
}
static void
Cssl_dtor(void *arg)
{
lcbio_CSSL *cs = arg;
assert(SLLIST_IS_EMPTY(&cs->writes));
lcbio_timer_destroy(cs->as_read);
lcbio_timer_destroy(cs->as_write);
iotssl_destroy_common((lcbio_XSSL *)cs);
free(cs->wctx_cached);
free(arg);
}
lcbio_pTABLE
lcbio_Cssl_new(lcbio_pTABLE orig, lcb_sockdata_t *sd, SSL_CTX *sctx)
{
lcbio_CSSL *ret = calloc(1, sizeof(*ret));
lcbio_pTABLE iot = &ret->base_;
ret->sd = sd;
ret->as_read = lcbio_timer_new(orig, ret, (void (*)(void*))appdata_read);
ret->as_write = lcbio_timer_new(orig, ret, async_write);
ret->base_.dtor = Cssl_dtor;
iot->u_io.completion.read2 = Cssl_read2;
iot->u_io.completion.write2 = Cssl_write2;
iot->u_io.completion.close = Cssl_close;
iotssl_init_common((lcbio_XSSL *)ret, orig, sctx);
return iot;
}