OSSP CVS Repository

ossp - ossp-pkg/sio/sio_bio.c
Not logged in
[Honeypot]  [Browse]  [Directory]  [Home]  [Login
[Reports]  [Search]  [Ticket]  [Timeline
  [Raw

ossp-pkg/sio/sio_bio.c
/*
**  OSSP sio - Stream I/O
**  Copyright (c) 2002-2005 Cable & Wireless <http://www.cw.com/>
**  Copyright (c) 2002-2005 The OSSP Project <http://www.ossp.org/>
**  Copyright (c) 2002-2005 Ralf S. Engelschall <rse@engelschall.com>
**
**  This file is part of OSSP sio, a layered stream I/O library
**  which can be found at http://www.ossp.org/pkg/lib/sio/.
**
**  Permission to use, copy, modify, and distribute this software for
**  any purpose with or without fee is hereby granted, provided that
**  the above copyright notice and this permission notice appear in all
**  copies.
**
**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
**  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
**  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
**  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
**  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
**  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
**  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
**  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
**  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
**  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
**  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
**  SUCH DAMAGE.
**
**  sio_bio.c: OpenSSL BIO stage
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#if ENABLE_BIO

#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "al.h"
#include "sio.h"

#include <openssl/bio.h>
#include <openssl/err.h>

typedef enum {
    INIT,
    LOWER,
    UPPER
} state_t;

typedef struct {
    state_t      state;
    int          isoutput;
    al_t        *in_buf;
    al_t        *out_buf;
    BIO         *bio;
    BIO         *nbio;
    al_t        *al_in, *al_out;   /* upstream buffers */
    int          issink;
    int          freebio;
    size_t       inputsize;
    al_label_t   data_label;
    al_label_t   eof_label;
    al_label_t   error_label;
    char         eof;
    char         error;
    size_t       total;
    int          needs_input;
    int          eof_reached;
    int          write_error;
    int          flush_upper;
    int          flush_lower;
} private_t;

/********************************************************************/

static
int b_new(BIO *bi)
{
    bi->init  = 0;
    bi->num   = -1;
    bi->flags = 0;
    bi->ptr   = NULL;
    return 1;
}

static
int b_free(BIO *a)
{
    if (a == NULL)
        return 0;
    return 1;
}

static
long b_pending(private_t *my)
{
    return my ? al_bytes(my->al_in) : 0;
}
static
long b_wpending(private_t *my)
{
    return my ? al_bytes(my->al_out) : 0;
}
static
long b_setflush(private_t *my)
{
    if (my) my->flush_upper = 1;
    return 1;
}
static
long b_geteof(private_t *my)
{
    return my->eof_reached;
}
static
long b_ctrl(BIO *b, int cmd, long num, void *ptr)
{
    long ret = 1;

    switch (cmd) {
    case BIO_CTRL_PENDING:
        ret = b_pending((private_t *)b->ptr);
        break;
    case BIO_CTRL_WPENDING:
        ret = b_wpending((private_t *)b->ptr);
        break;
    case BIO_CTRL_FLUSH:
        ret = b_setflush((private_t *)b->ptr);
        break;
    case BIO_CTRL_EOF:
        ret = b_geteof((private_t *)b->ptr);
        break;
    case BIO_C_SET_FD:
        b->ptr  = ptr;
        b->init = 1;
        ret = 1;
        break;
    case BIO_CTRL_RESET:
    case BIO_CTRL_SET:
    case BIO_CTRL_SET_CLOSE:
    case BIO_CTRL_DUP:
        ret = 1;
        break;
    case BIO_CTRL_GET_CLOSE:
    case BIO_CTRL_INFO:
    case BIO_CTRL_GET:
    default:
        ret = 0;
        break;
    }

    return ret;
}

static
int b_read(BIO *b, char *out, int outl)
{
    private_t *my = (private_t *)b->ptr;
    size_t n, m;
    al_label_t label;

    m = 0;
    if (al_bytes(my->al_in) > 0) {
        al_firstlabel(my->al_in, 0, 1, AL_FORWARD, NULL, &label);
        if (label == my->data_label) {
            al_flatten(my->al_in, 0, outl, AL_FORWARD_SPAN, label, out, &n);
            m = n;
        } else {
            al_flatten(my->al_in, 0, outl, AL_FORWARD_SPAN, label, NULL, &n);
            my->eof_reached = 1;
            m = 0;
        }
        al_splice(my->al_in, 0, n, NULL, NULL);
    }

    BIO_clear_retry_flags(b);
    if (m == 0)
        BIO_set_retry_read(b);

    return m;
}

static
int b_write(BIO *b, const char *in, int inl)
{
    private_t *my = (private_t *)b->ptr;

    al_append_bytes(my->al_out, in, inl, my->data_label);

    return inl;
}

static
int b_puts(BIO *b, const char *s)
{
    return b_write(b, s, strlen(s));
}

static
BIO_METHOD b_method = {
    BIO_TYPE_NONE,
    "SIO SSL",
    b_write,
    b_read,
    b_puts,
    NULL,
    b_ctrl,
    b_new,
    b_free,
    NULL
};

/********************************************************************/

/*
 * create stage
 *
 * allocate private instance data
 */
static
sio_rc_t siobio_init(sio_t *sio, void **up)
{
    private_t *my;

    my = (private_t *)malloc(sizeof(private_t));
    if (my == NULL)
        return SIO_ERR_MEM;

    my->bio           = NULL;
    my->inputsize     = 512;
    my->issink        = 0;
    my->freebio       = 0;
    my->nbio          = NULL;
    my->eof           = '\0';
    my->error         = '\0';

    sio_label(sio, SIO_LN_DATA,  &my->data_label);
    sio_label(sio, SIO_LN_EOF,   &my->eof_label);
    sio_label(sio, SIO_LN_ERROR, &my->error_label);

    *up = my;

    return SIO_OK;
}

/*
 * configure stage
 *
 * pass two void pointers
 */
static
sio_rc_t siobio_configure(sio_t *sio, void *u, void *obj, void *val)
{
    private_t *my = (private_t *)u;
    const char *name = (const char *)obj;
    int v;

    if (!strcmp(name, "bio")) {
        my->bio       = (BIO *)val;
    } else if (!strcmp(name, "inputsize")) {
        v = *(int *)val;
        if (v <= 0)
            return SIO_ERR_ARG;
        my->inputsize = v;
    } else if (!strcmp(name, "issink")) {
        my->issink    = *(int *)val;
    } else if (!strcmp(name, "freebio")) {
        my->freebio   = *(int *)val;
    } else {
        return SIO_ERR_ARG;
    }

    return SIO_OK;
}

/*
 * destroy stage
 */
static
sio_rc_t siobio_cleanup(sio_t *sio, void *u)
{
    private_t *my = (private_t *)u;

    free(my);

    return SIO_OK;
}

static
sio_rc_t siobio_openr(sio_t *sio, al_t *al, void *u)
{
    return SIO_OK;
}

static
sio_rc_t siobio_closer(sio_t *sio, al_t *al, void *u)
{
    return SIO_OK;
}

static
sio_rc_t siobio_openw(sio_t *sio, al_t *al, void *u)
{
    private_t *my = (private_t *)u;

    if (my->bio == NULL)
        return SIO_ERR_ARG;

    if (al_create(&my->al_in) != AL_OK)
        return SIO_ERR_INT;
    if (al_create(&my->al_out) != AL_OK) {
        al_destroy(my->al_in); my->al_in = NULL;
        return SIO_ERR_INT;
    }
    if (al_create(&my->in_buf) != AL_OK) {
        al_destroy(my->al_out); my->al_out = NULL;
        al_destroy(my->al_in); my->al_in = NULL;
        return SIO_ERR_INT;
    }
    if (al_create(&my->out_buf) != AL_OK) {
        al_destroy(my->in_buf); my->in_buf = NULL;
        al_destroy(my->al_out); my->al_out = NULL;
        al_destroy(my->al_in); my->al_in = NULL;
        return SIO_ERR_INT;
    }

    if (!my->issink) {
        my->nbio = BIO_new(&b_method);
        BIO_ctrl(my->nbio, BIO_C_SET_FD, 0, (void *)my);
        BIO_push(my->bio, my->nbio);
    }

    my->eof_reached   = 0;
    my->needs_input   = 0;
    my->write_error   = 0;

    my->flush_upper   = 0;
    my->flush_lower   = 0;

    my->state = INIT;

    return SIO_OK;
}

static
sio_rc_t siobio_closew(sio_t *sio, al_t *al, void *u)
{
    private_t *my = (private_t *)u;

    if (my->nbio != NULL) {
        BIO_pop(my->bio);
        BIO_free(my->nbio);
        my->nbio = NULL;
    }

    al_destroy(my->out_buf); my->out_buf = NULL;
    al_destroy(my->in_buf); my->in_buf = NULL;
    al_destroy(my->al_out); my->al_out = NULL;
    al_destroy(my->al_in); my->al_in = NULL;

    return SIO_OK;
}

/********************************************************************/

/*
 * auto-destructor for allocated input buffer
 */
static
void freebiobuf(char *p, size_t n, void *u)
{
    free(p);
}

/*
 * chunk traversal of output buffer
 *
 * counts my->total
 * sets my->needs_input
 */
static
al_rc_t siobio_write_chunk(al_chunk_t *alc, void *u)
{
    private_t *my = (private_t *)u;
    int        n  = al_chunk_len(alc);
    al_rc_t arc   = AL_OK;

    if (al_same_label(alc, my->data_label)) {
        char *p = al_chunk_ptr(alc, 0);
        int   i, t;

        for (t=0; t<n; t+=i) {
            i = my->bio ? BIO_write(my->bio, p+t, n-t) : (n-t);
            if (i <= 0) {
                if (!BIO_should_retry(my->bio)) {
                    my->needs_input = 1;
                    if (my->eof_reached)
                        my->write_error = 1;
                }
                arc = AL_ERR_EOF;
                break;
            }
            my->total       += i;
        }
    } else {
        my->total += n;
        my->flush_lower = 1;
    }

    return arc;
}

/********************************************************************/

/* UPSTREAM layer */

static
sio_rc_t siobio_input_upper(private_t *my, al_t *al)
{
    size_t n;
    
    /* copy global al into input buffer */
    n = al_bytes(al);
    al_splice(al, 0, n, NULL, my->al_in);

    /* flush output */
    if (my->flush_upper)
        return SIO_SCHED_CROSS;

    /* transport data to lower layers */
    if (al_bytes(my->al_in) <= 0)
        return SIO_SCHED_UP;

    if (!my->isoutput) {
        my->state = LOWER;
        return SIO_SCHED_LOOP;
    }
    return SIO_SCHED_CROSS;
}

static
sio_rc_t siobio_output_upper(private_t *my, al_t *al)
{
    /* copy output buffer to global al */
    my->flush_upper = 0;
    al_splice(al, al_bytes(al), 0, my->al_out, NULL);

    /* transport data to upper layers */
    if (al_bytes(al) > 0)
        return SIO_SCHED_UP;

    /* lower layers signalled they need input */
    if (my->needs_input) {
        my->needs_input = 0;
        /* place input buffer on global al so input routines
         * can pick it from there again */
        al_splice(al, 0, al_bytes(al), my->al_in, NULL);
        return SIO_SCHED_CROSS;
    }

    if (my->isoutput) {
        my->state = LOWER;
        return SIO_SCHED_LOOP;
    }
    return SIO_SCHED_CROSS;
}

/* DOWNSTREAM layer */
static
sio_rc_t siobio_input_lower(private_t *my, al_t *al)
{
    al_splice(al, al_bytes(al), 0, my->in_buf, NULL);
    if (al_bytes(al) > 0 || my->eof_reached) {
        my->state = INIT;
        return SIO_SCHED_DOWN;
    }

    my->state = UPPER;
    return SIO_SCHED_LOOP;
}

static
sio_rc_t siobio_output_lower(private_t *my, al_t *al)
{
    al_splice(al, 0, al_bytes(al), NULL, my->out_buf);
    if (al_bytes(my->out_buf) <= 0) {
        my->state = INIT;
        return SIO_SCHED_DOWN;
    }

    my->state = UPPER;
    return SIO_SCHED_LOOP;
}

/* BIO layer */
static
void siobio_bio_read(private_t *my)
{
    char *p;
    int n, m;

    n = BIO_pending(my->bio);
    if (n <= 0 || (size_t)n > my->inputsize)
        n = my->inputsize;
    p = malloc(n);
    assert(p != NULL);

    if (my->bio) {
        do {
            m = BIO_read(my->bio, p, n);
        } while (m <= 0 && BIO_should_retry(my->bio));
        if (m == -1 && !my->eof_reached) {
            my->flush_upper = 1;
            m = 0;
        }
    } else
        m = -2;

    if (m < 0) {
        free(p);
        if (m < -1)
            al_append_bytes(my->in_buf, &my->error,
                            sizeof(my->error), my->error_label);
        else
            al_append_bytes(my->in_buf, &my->eof,
                            sizeof(my->eof), my->eof_label);
    } else if (m > 0)
        al_attach_buffer(my->in_buf, p, m, my->data_label, freebiobuf, NULL);
}

static
void siobio_bio_write(private_t *my)
{
    my->needs_input = 0;
    my->total       = 0;
    al_traverse_cb(my->out_buf, 0, al_bytes(my->out_buf),
                   AL_FORWARD, NULL,
                   siobio_write_chunk, (void *)my);
    al_splice(my->out_buf, 0, my->total, NULL, NULL);

    if (!my->write_error && my->flush_lower) {
        my->flush_upper = 1;
        if (BIO_flush(my->bio) > 0)
            my->flush_lower = 0;
    }
}

/********************************************************************/

static
sio_rc_t siobio_input(sio_t *sio, al_t *al, void *u, sio_rc_t orc)
{
    private_t *my = (private_t *)u;
    sio_rc_t rc = SIO_ERR_INT;

    switch (my->state) {
    case INIT:
        my->isoutput = 0;
        my->state    = LOWER;
        rc = SIO_SCHED_LOOP;
        break;
    case LOWER:
        siobio_bio_read(my);
        rc = siobio_input_lower(my, al);
        if (my->eof_reached) {
            my->eof_reached = 0;
            my->state       = INIT;
            al_append_bytes(al, &my->eof, sizeof(my->eof), my->eof_label);
            return SIO_SCHED_DOWN;
        }
        break;
    case UPPER:
        rc = siobio_input_upper(my, al);
        break;
    }

    return rc;
}

static
sio_rc_t siobio_output(sio_t *sio, al_t *al, void *u, sio_rc_t orc)
{
    private_t *my = (private_t *)u;
    sio_rc_t rc = SIO_ERR_INT;

    switch (my->state) {
    case INIT:
        my->isoutput = 1;
        my->state    = LOWER;
        rc = SIO_SCHED_LOOP;
        break;
    case LOWER:
        rc = siobio_output_lower(my, al);
        siobio_bio_write(my);
        if (my->write_error) {
            my->write_error = 0;
            my->state       = INIT;
            al_splice(al, 0, al_bytes(al), NULL, NULL);
            return SIO_SCHED_DOWN;
        }
        break;
    case UPPER:
        rc = siobio_output_upper(my, al);
        break;
    }

    return rc;
}

static
sio_rc_t siobio_shutdown(sio_t *sio, void *u)
{
    private_t *my = (private_t *)u;

    if (my->freebio && my->bio != NULL) {
        BIO_free(my->bio);
        my->bio = NULL;
        my->flush_upper = 1;
        my->state       = UPPER;
        return SIO_OK;
    }

    return SIO_ERR_ARG;
}

sio_module_t sio_module_bio = {
    "bio",
    siobio_init,
    siobio_configure,
    siobio_cleanup,
    siobio_openr,
    siobio_closer,
    siobio_openw,
    siobio_closew,
    siobio_input,
    siobio_output,
    siobio_shutdown
};

#else

const char __sio_bio_c[] = "";

#endif /* ENABLE_BIO */


CVSTrac 2.0.1