#include "ssl_iot_common.h"
#include "simplestring.h"
#include <openssl/err.h>
typedef struct {
IOTSSL_COMMON_FIELDS
void *event;
void *arg;
short requested;
short fakewhich;
lcb_ioE_callback ucb;
int entered;
int closed;
lcb_socket_t fd;
lcbio_pTIMER as_fake;
lcb_SIZE last_nw;
} lcbio_ESSL;
#ifdef USE_EAGAIN
#define C_EAGAIN EWOULDBLOCK: case EAGAIN
#else
#define C_EAGAIN EWOULDBLOCK
#endif
#define ES_FROM_IOPS(iops) (lcbio_ESSL *)(IOTSSL_FROM_IOPS(iops))
#define MINIMUM(a,b) a < b ? a : b
static int maybe_error(lcbio_ESSL *es, int rv) {
return iotssl_maybe_error((lcbio_XSSL *)es, rv);
}
static void event_handler(lcb_socket_t, short, void *);
#define SCHEDULE_PENDING_SAFE(es) if (!(es)->entered) { schedule_pending(es); }
static void
schedule_pending(lcbio_ESSL *es)
{
short avail = LCB_WRITE_EVENT;
short wanted = 0;
IOTSSL_PENDING_PRECHECK(es->ssl);
if (IOTSSL_IS_PENDING(es->ssl)) {
avail |= LCB_READ_EVENT;
}
if (SSL_want_read(es->ssl)) {
wanted |= LCB_READ_EVENT;
}
if (BIO_ctrl_pending(es->wbio)) {
wanted |= LCB_WRITE_EVENT;
}
es->fakewhich = avail;
if (es->fakewhich & es->requested) {
lcbio_async_signal(es->as_fake);
}
if ((es->requested & LCB_READ_EVENT) && (avail & LCB_READ_EVENT) == 0) {
wanted |= LCB_READ_EVENT;
}
IOT_V0EV(es->orig).watch(
IOT_ARG(es->orig), es->fd, es->event, wanted, es, event_handler);
}
static int
read_ssl_data(lcbio_ESSL *es)
{
BUF_MEM *rmb;
int nr;
lcbio_pTABLE iot = es->orig;
BIO_get_mem_ptr(es->rbio, &rmb);
while (1) {
BIO_clear_retry_flags(es->rbio);
iotssl_bm_reserve(rmb);
nr = IOT_V0IO(iot).recv(IOT_ARG(iot), es->fd,
rmb->data + rmb->length, rmb->max - rmb->length, 0);
if (nr > 0) {
rmb->length += nr;
} else if (nr == 0) {
es->closed = 1;
return -1;
} else {
switch (IOT_ERRNO(iot)) {
case C_EAGAIN:
return 0;
case EINTR:
continue;
default:
return -1;
}
}
}
return 0;
}
static int
flush_ssl_data(lcbio_ESSL *es)
{
BUF_MEM *wmb;
char *tmp_p;
int tmp_len, nw;
lcbio_pTABLE iot = es->orig;
BIO_get_mem_ptr(es->wbio, &wmb);
tmp_p = wmb->data;
tmp_len = wmb->length;
while (tmp_len) {
nw = IOT_V0IO(iot).send(IOT_ARG(iot), es->fd, tmp_p, tmp_len, 0);
if (nw > 0) {
tmp_len -= nw;
tmp_p += nw;
continue;
} else if (nw == 0) {
return -1;
} else {
switch (IOT_ERRNO(iot)) {
case C_EAGAIN:
goto GT_WRITE_DONE;
case EINTR:
continue;
default:
return -1;
}
}
}
GT_WRITE_DONE:
while (wmb->length > (size_t)tmp_len) {
char dummy[4096];
unsigned to_read = MINIMUM(wmb->length-tmp_len, sizeof dummy);
BIO_read(es->wbio, dummy, to_read);
}
BIO_clear_retry_flags(es->wbio);
return 0;
}
static void
event_handler(lcb_socket_t fd, short which, void *arg)
{
lcbio_ESSL *es = arg;
int rv = 0;
int u_which;
es->entered++;
if (which & LCB_READ_EVENT) {
rv = read_ssl_data(es);
}
if (rv == 0 && (which & LCB_WRITE_EVENT)) {
rv = flush_ssl_data(es);
}
if (rv == -1) {
es->error = 1;
IOT_V0EV(es->orig).watch(
IOT_ARG(es->orig), es->fd, es->event, 0, NULL, NULL);
if (es->requested && es->ucb) {
es->ucb(fd, es->requested, es->arg);
}
es->entered--;
return;
}
u_which = 0;
if (es->requested & LCB_READ_EVENT) {
u_which |= LCB_READ_EVENT;
}
if (es->requested & LCB_WRITE_EVENT) {
u_which |= LCB_WRITE_EVENT;
}
if (es->ucb && u_which) {
es->ucb(fd, u_which & es->requested, es->arg);
}
if (es->fd == -1) {
es->entered--;
return;
}
es->entered--;
schedule_pending(es);
}
static void
fake_signal(void *arg)
{
lcbio_ESSL *es = arg;
short which = es->fakewhich;
es->fakewhich = 0;
es->entered++;
which &= es->requested;
if (which && es->ucb) {
es->ucb(es->fd, which, es->arg);
}
es->entered--;
schedule_pending(es);
}
static int
start_watch(lcb_io_opt_t iops, lcb_socket_t sock, void *event, short which,
void *uarg, lcb_ioE_callback callback)
{
lcbio_ESSL *es = ES_FROM_IOPS(iops);
es->arg = uarg;
es->requested = which;
es->ucb = callback;
SCHEDULE_PENDING_SAFE(es);
(void)event;
(void)sock;
return 0;
}
static void
stop_watch(lcb_io_opt_t iops, lcb_socket_t sock, void *event)
{
start_watch(iops, sock, event, 0, NULL, NULL);
}
static lcb_ssize_t
Essl_recv(lcb_io_opt_t iops, lcb_socket_t sock, void *buf, lcb_size_t nbuf,
int ign)
{
lcbio_ESSL *es = ES_FROM_IOPS(iops);
int rv = SSL_read(es->ssl, buf, nbuf);
if (es->error) {
IOTSSL_ERRNO(es) = EINVAL;
return -1;
}
if (rv >= 0) {
return rv;
} else if (es->closed) {
return 0;
} else if (maybe_error(es, rv) != 0) {
IOTSSL_ERRNO(es) = EINVAL;
} else {
IOTSSL_ERRNO(es) = EWOULDBLOCK;
}
(void) ign; (void) sock;
return -1;
}
static lcb_ssize_t
Essl_send(lcb_io_opt_t iops, lcb_socket_t sock, const void *buf, lcb_size_t nbuf,
int ign)
{
lcbio_ESSL *es = ES_FROM_IOPS(iops);
int rv;
(void) ign; (void) sock;
if (es->error) {
IOTSSL_ERRNO(es) = EINVAL;
return -1;
}
rv = SSL_write(es->ssl, buf, nbuf);
if (rv >= 0) {
SCHEDULE_PENDING_SAFE(es);
return rv;
} else if (maybe_error(es, rv)) {
IOTSSL_ERRNO(es) = EINVAL;
return -1;
} else {
IOTSSL_ERRNO(es) = EWOULDBLOCK;
return -1;
}
}
static lcb_ssize_t
Essl_recvv(lcb_io_opt_t iops, lcb_socket_t sock, lcb_IOV *iov, lcb_size_t niov) {
(void) niov;
return Essl_recv(iops, sock, iov->iov_base, iov->iov_len, 0);
}
static lcb_ssize_t
Essl_sendv(lcb_io_opt_t iops, lcb_socket_t sock, lcb_IOV *iov, lcb_size_t niov) {
(void) niov;
return Essl_send(iops, sock, iov->iov_base, iov->iov_len, 0);
}
static void
Essl_close(lcb_io_opt_t iops, lcb_socket_t fd)
{
lcbio_ESSL *es = ES_FROM_IOPS(iops);
IOT_V0IO(es->orig).close(IOT_ARG(es->orig), fd);
es->fd = -1;
}
static void
Essl_dtor(void *arg)
{
lcbio_ESSL *es = arg;
IOT_V0EV(es->orig).destroy(IOT_ARG(es->orig), es->event);
lcbio_timer_destroy(es->as_fake);
iotssl_destroy_common((lcbio_XSSL *)es);
free(es);
}
lcbio_pTABLE
lcbio_Essl_new(lcbio_pTABLE orig, lcb_socket_t fd, SSL_CTX *sctx)
{
lcbio_ESSL *es = calloc(1, sizeof(*es));
lcbio_TABLE *iot = &es->base_;
es->fd = fd;
es->as_fake = lcbio_timer_new(orig, es, fake_signal);
es->event = IOT_V0EV(orig).create(IOT_ARG(orig));
iot->u_io.v0.ev.watch = start_watch;
iot->u_io.v0.ev.cancel = stop_watch;
iot->u_io.v0.io.recv = Essl_recv;
iot->u_io.v0.io.send = Essl_send;
iot->u_io.v0.io.recvv = Essl_recvv;
iot->u_io.v0.io.sendv = Essl_sendv;
iot->u_io.v0.io.close = Essl_close;
iot->dtor = Essl_dtor;
iotssl_init_common((lcbio_XSSL *)es, orig, sctx);
return iot;
}