

#include <stdio.h>
#include <errno.h>
#include "bio_local.h"
#include "internal/cryptlib.h"
#include "internal/safe_math.h"

#if !defined(OPENSSL_NO_DGRAM) && !defined(OPENSSL_NO_SOCK)

OSSL_SAFE_MATH_UNSIGNED(size_t, size_t)


struct ring_buf {
    unsigned char *start; 
    size_t len; 
    size_t count; 
    
    size_t idx[2]; 
};

static int ring_buf_init(struct ring_buf *r, size_t nbytes)
{
    r->start = OPENSSL_malloc(nbytes);
    if (r->start == NULL)
        return 0;

    r->len = nbytes;
    r->idx[0] = r->idx[1] = r->count = 0;
    return 1;
}

static void ring_buf_destroy(struct ring_buf *r)
{
    OPENSSL_free(r->start);
    r->start    = NULL;
    r->len      = 0;
    r->count    = 0;
}


static void ring_buf_head_tail(struct ring_buf *r, int idx, uint8_t **buf, size_t *len)
{
    size_t max_len = r->len - r->idx[idx];

    if (idx == 0 && max_len > r->len - r->count)
        max_len = r->len - r->count;
    if (idx == 1 && max_len > r->count)
        max_len = r->count;

    *buf = (uint8_t *)r->start + r->idx[idx];
    *len = max_len;
}

#define ring_buf_head(r, buf, len) ring_buf_head_tail((r), 0, (buf), (len))
#define ring_buf_tail(r, buf, len) ring_buf_head_tail((r), 1, (buf), (len))


static void ring_buf_push_pop(struct ring_buf *r, int idx, size_t num_bytes)
{
    size_t new_idx;

    
    if (!ossl_assert(num_bytes <= r->len - r->idx[idx]))
        return;

    
    if (!ossl_assert(idx != 0 ? num_bytes <= r->count
                              : num_bytes + r->count <= r->len))
        return;

    
    new_idx = r->idx[idx] + num_bytes;
    if (new_idx == r->len)
        new_idx = 0;

    r->idx[idx] = new_idx;
    if (idx != 0)
        r->count -= num_bytes;
    else
        r->count += num_bytes;
}

#define ring_buf_push(r, num_bytes) ring_buf_push_pop((r), 0, (num_bytes))
#define ring_buf_pop(r, num_bytes) ring_buf_push_pop((r), 1, (num_bytes))

static void ring_buf_clear(struct ring_buf *r)
{
    r->idx[0] = r->idx[1] = r->count = 0;
}

static int ring_buf_resize(struct ring_buf *r, size_t nbytes)
{
    unsigned char *new_start;

    if (r->start == NULL)
        return ring_buf_init(r, nbytes);

    if (nbytes == r->len)
        return 1;

    if (r->count > 0 && nbytes < r->len)
        
        return 0;

    new_start = OPENSSL_realloc(r->start, nbytes);
    if (new_start == NULL)
        return 0;

    
    if (r->count > 0) {
        if (r->idx[0] <= r->idx[1]) {
            size_t offset = nbytes - r->len;

            memmove(new_start + r->idx[1] + offset, new_start + r->idx[1],
                    r->len - r->idx[1]);
            r->idx[1] += offset;
        }
    } else {
        
        r->idx[0] = r->idx[1] = 0;
    }

    r->start = new_start;
    r->len = nbytes;

    return 1;
}


struct bio_dgram_pair_st;
static int dgram_pair_write(BIO *bio, const char *buf, int sz_);
static int dgram_pair_read(BIO *bio, char *buf, int sz_);
static int dgram_mem_read(BIO *bio, char *buf, int sz_);
static long dgram_pair_ctrl(BIO *bio, int cmd, long num, void *ptr);
static long dgram_mem_ctrl(BIO *bio, int cmd, long num, void *ptr);
static int dgram_pair_init(BIO *bio);
static int dgram_mem_init(BIO *bio);
static int dgram_pair_free(BIO *bio);
static int dgram_pair_sendmmsg(BIO *b, BIO_MSG *msg, size_t stride,
                               size_t num_msg, uint64_t flags,
                               size_t *num_processed);
static int dgram_pair_recvmmsg(BIO *b, BIO_MSG *msg, size_t stride,
                               size_t num_msg, uint64_t flags,
                               size_t *num_processed);

static int dgram_pair_ctrl_destroy_bio_pair(BIO *bio1);
static size_t dgram_pair_read_inner(struct bio_dgram_pair_st *b, uint8_t *buf,
                                    size_t sz);

#define BIO_MSG_N(array, n) (*(BIO_MSG *)((char *)(array) + (n)*stride))

static const BIO_METHOD dgram_pair_method = {
    BIO_TYPE_DGRAM_PAIR,
    "BIO dgram pair",
    bwrite_conv,
    dgram_pair_write,
    bread_conv,
    dgram_pair_read,
    NULL, 
    NULL, 
    dgram_pair_ctrl,
    dgram_pair_init,
    dgram_pair_free,
    NULL, 
    dgram_pair_sendmmsg,
    dgram_pair_recvmmsg,
};

static const BIO_METHOD dgram_mem_method = {
    BIO_TYPE_DGRAM_MEM,
    "BIO dgram mem",
    bwrite_conv,
    dgram_pair_write,
    bread_conv,
    dgram_mem_read,
    NULL, 
    NULL, 
    dgram_mem_ctrl,
    dgram_mem_init,
    dgram_pair_free,
    NULL, 
    dgram_pair_sendmmsg,
    dgram_pair_recvmmsg,
};

const BIO_METHOD *BIO_s_dgram_pair(void)
{
    return &dgram_pair_method;
}

const BIO_METHOD *BIO_s_dgram_mem(void)
{
    return &dgram_mem_method;
}

struct dgram_hdr {
    size_t len; 
    BIO_ADDR src_addr, dst_addr; 
};

struct bio_dgram_pair_st {
    
    BIO *peer;
    
    struct ring_buf rbuf;
    
    size_t req_buf_len;
    
    size_t mtu;
    
    uint32_t cap;
    
    CRYPTO_RWLOCK *lock;
    unsigned int no_trunc          : 1; 
    unsigned int local_addr_enable : 1; 
    unsigned int role              : 1; 
    unsigned int grows_on_write    : 1; 
};

#define MIN_BUF_LEN (1024)

#define is_dgram_pair(b) (b->peer != NULL)

static int dgram_pair_init(BIO *bio)
{
    struct bio_dgram_pair_st *b = OPENSSL_zalloc(sizeof(*b));

    if (b == NULL)
        return 0;

    b->mtu         = 1472;    
    
    b->req_buf_len = 9 * (sizeof(struct dgram_hdr) + b->mtu);

    b->lock = CRYPTO_THREAD_lock_new();
    if (b->lock == NULL) {
        OPENSSL_free(b);
        return 0;
    }

    bio->ptr = b;
    return 1;
}

static int dgram_mem_init(BIO *bio)
{
    struct bio_dgram_pair_st *b;

    if (!dgram_pair_init(bio))
        return 0;

    b = bio->ptr;

    if (ring_buf_init(&b->rbuf, b->req_buf_len) == 0) {
        ERR_raise(ERR_LIB_BIO, ERR_R_BIO_LIB);
        return 0;
    }

    b->grows_on_write = 1;

    bio->init = 1;
    return 1;
}

static int dgram_pair_free(BIO *bio)
{
    struct bio_dgram_pair_st *b;

    if (bio == NULL)
        return 0;

    b = bio->ptr;
    if (!ossl_assert(b != NULL))
        return 0;

    
    dgram_pair_ctrl_destroy_bio_pair(bio);

    CRYPTO_THREAD_lock_free(b->lock);
    OPENSSL_free(b);
    return 1;
}


static int dgram_pair_ctrl_make_bio_pair(BIO *bio1, BIO *bio2)
{
    struct bio_dgram_pair_st *b1, *b2;

    
    if (bio1 == NULL || bio2 == NULL) {
        ERR_raise(ERR_LIB_BIO, BIO_R_INVALID_ARGUMENT);
        return 0;
    }

    
    if (bio1->method != &dgram_pair_method || bio2->method != &dgram_pair_method) {
        ERR_raise_data(ERR_LIB_BIO, BIO_R_INVALID_ARGUMENT,
                       "both BIOs must be BIO_dgram_pair");
        return 0;
    }

    b1 = bio1->ptr;
    b2 = bio2->ptr;

    if (!ossl_assert(b1 != NULL && b2 != NULL)) {
        ERR_raise(ERR_LIB_BIO, BIO_R_UNINITIALIZED);
        return 0;
    }

    
    if (b1->peer != NULL || b2->peer != NULL) {
        ERR_raise_data(ERR_LIB_BIO, BIO_R_IN_USE,
                       "cannot associate a BIO_dgram_pair which is already in use");
        return 0;
    }

    if (!ossl_assert(b1->req_buf_len >= MIN_BUF_LEN
                        && b2->req_buf_len >= MIN_BUF_LEN)) {
        ERR_raise(ERR_LIB_BIO, BIO_R_UNINITIALIZED);
        return 0;
    }

    if (b1->rbuf.len != b1->req_buf_len)
        if (ring_buf_init(&b1->rbuf, b1->req_buf_len) == 0) {
            ERR_raise(ERR_LIB_BIO, ERR_R_BIO_LIB);
            return 0;
        }

    if (b2->rbuf.len != b2->req_buf_len)
        if (ring_buf_init(&b2->rbuf, b2->req_buf_len) == 0) {
            ERR_raise(ERR_LIB_BIO, ERR_R_BIO_LIB);
            ring_buf_destroy(&b1->rbuf);
            return 0;
        }

    b1->peer    = bio2;
    b2->peer    = bio1;
    b1->role    = 0;
    b2->role    = 1;
    bio1->init  = 1;
    bio2->init  = 1;
    return 1;
}


static int dgram_pair_ctrl_destroy_bio_pair(BIO *bio1)
{
    BIO *bio2;
    struct bio_dgram_pair_st *b1 = bio1->ptr, *b2;

    ring_buf_destroy(&b1->rbuf);
    bio1->init = 0;

    
    if (b1->peer == NULL)
        return 1;

    bio2 = b1->peer;
    b2 = bio2->ptr;

    
    if (!ossl_assert(b2->peer == bio1))
        return 0;

    
    ring_buf_destroy(&b2->rbuf);

    bio2->init = 0;
    b1->peer = NULL;
    b2->peer = NULL;
    return 1;
}


static int dgram_pair_ctrl_eof(BIO *bio)
{
    struct bio_dgram_pair_st *b = bio->ptr, *peerb;

    if (!ossl_assert(b != NULL))
        return -1;

    
    if (!bio->init)
        return 1;
    if (!is_dgram_pair(b))
        return 0;


    peerb = b->peer->ptr;
    if (!ossl_assert(peerb != NULL))
        return -1;

    
    return 0;
}


static int dgram_pair_ctrl_set_write_buf_size(BIO *bio, size_t len)
{
    struct bio_dgram_pair_st *b = bio->ptr;

    
    if (b->peer != NULL) {
        ERR_raise(ERR_LIB_BIO, BIO_R_IN_USE);
        return 0;
    }

    
    if (len < MIN_BUF_LEN)
        len = MIN_BUF_LEN;

    if (b->rbuf.start != NULL) {
        if (!ring_buf_resize(&b->rbuf, len))
            return 0;
    }

    b->req_buf_len = len;
    b->grows_on_write = 0;
    return 1;
}


static int dgram_pair_ctrl_reset(BIO *bio)
{
    struct bio_dgram_pair_st *b = bio->ptr;

    ring_buf_clear(&b->rbuf);
    return 1;
}


static size_t dgram_pair_ctrl_pending(BIO *bio)
{
    size_t saved_idx, saved_count;
    struct bio_dgram_pair_st *b = bio->ptr, *readb;
    struct dgram_hdr hdr;
    size_t l;

    
    if (!bio->init)
        return 0;
    if (is_dgram_pair(b))
        readb = b->peer->ptr;
    else
        readb = b;

    if (CRYPTO_THREAD_write_lock(readb->lock) == 0)
        return 0;

    saved_idx   = readb->rbuf.idx[1];
    saved_count = readb->rbuf.count;

    l = dgram_pair_read_inner(readb, (uint8_t *)&hdr, sizeof(hdr));

    readb->rbuf.idx[1] = saved_idx;
    readb->rbuf.count  = saved_count;

    CRYPTO_THREAD_unlock(readb->lock);

    if (!ossl_assert(l == 0 || l == sizeof(hdr)))
        return 0;

    return l > 0 ? hdr.len : 0;
}


static size_t dgram_pair_ctrl_get_write_guarantee(BIO *bio)
{
    size_t l;
    struct bio_dgram_pair_st *b = bio->ptr;

    if (CRYPTO_THREAD_read_lock(b->lock) == 0)
        return 0;

    l = b->rbuf.len - b->rbuf.count;
    if (l >= sizeof(struct dgram_hdr))
        l -= sizeof(struct dgram_hdr);

    
    if (l < b->mtu)
        l = 0;

    CRYPTO_THREAD_unlock(b->lock);
    return l;
}


static int dgram_pair_ctrl_get_local_addr_cap(BIO *bio)
{
    struct bio_dgram_pair_st *b = bio->ptr, *readb;

    if (!bio->init)
        return 0;

    if (is_dgram_pair(b))
        readb = b->peer->ptr;
    else
        readb = b;

    return (~readb->cap & (BIO_DGRAM_CAP_HANDLES_SRC_ADDR
                           | BIO_DGRAM_CAP_PROVIDES_DST_ADDR)) == 0;
}


static int dgram_pair_ctrl_get_effective_caps(BIO *bio)
{
    struct bio_dgram_pair_st *b = bio->ptr, *peerb;

    if (b->peer == NULL)
        return 0;

    peerb = b->peer->ptr;

    return peerb->cap;
}


static uint32_t dgram_pair_ctrl_get_caps(BIO *bio)
{
    struct bio_dgram_pair_st *b = bio->ptr;

    return b->cap;
}


static int dgram_pair_ctrl_set_caps(BIO *bio, uint32_t caps)
{
    struct bio_dgram_pair_st *b = bio->ptr;

    b->cap = caps;
    return 1;
}


static int dgram_pair_ctrl_get_local_addr_enable(BIO *bio)
{
    struct bio_dgram_pair_st *b = bio->ptr;

    return b->local_addr_enable;
}


static int dgram_pair_ctrl_set_local_addr_enable(BIO *bio, int enable)
{
    struct bio_dgram_pair_st *b = bio->ptr;

    if (dgram_pair_ctrl_get_local_addr_cap(bio) == 0)
        return 0;

    b->local_addr_enable = (enable != 0 ? 1 : 0);
    return 1;
}


static int dgram_pair_ctrl_get_mtu(BIO *bio)
{
    struct bio_dgram_pair_st *b = bio->ptr;

    return b->mtu;
}


static int dgram_pair_ctrl_set_mtu(BIO *bio, size_t mtu)
{
    struct bio_dgram_pair_st *b = bio->ptr, *peerb;

    b->mtu = mtu;

    if (b->peer != NULL) {
        peerb = b->peer->ptr;
        peerb->mtu = mtu;
    }

    return 1;
}


static long dgram_mem_ctrl(BIO *bio, int cmd, long num, void *ptr)
{
    long ret = 1;
    struct bio_dgram_pair_st *b = bio->ptr;

    if (!ossl_assert(b != NULL))
        return 0;

    switch (cmd) {
    
    case BIO_C_SET_WRITE_BUF_SIZE: 
        ret = (long)dgram_pair_ctrl_set_write_buf_size(bio, (size_t)num);
        break;

    
    case BIO_C_GET_WRITE_BUF_SIZE: 
        ret = (long)b->req_buf_len;
        break;

    
    case BIO_CTRL_RESET: 
        dgram_pair_ctrl_reset(bio);
        break;

    
    case BIO_C_GET_WRITE_GUARANTEE: 
        ret = (long)dgram_pair_ctrl_get_write_guarantee(bio);
        break;

    
    case BIO_CTRL_PENDING: 
        ret = (long)dgram_pair_ctrl_pending(bio);
        break;

    
    case BIO_CTRL_FLUSH: 
        break;

    
    case BIO_CTRL_DGRAM_GET_NO_TRUNC: 
        ret = (long)b->no_trunc;
        break;

    
    case BIO_CTRL_DGRAM_SET_NO_TRUNC: 
        b->no_trunc = (num > 0);
        break;

    
    case BIO_CTRL_DGRAM_GET_LOCAL_ADDR_ENABLE: 
        *(int *)ptr = (int)dgram_pair_ctrl_get_local_addr_enable(bio);
        break;

    
    case BIO_CTRL_DGRAM_SET_LOCAL_ADDR_ENABLE: 
        ret = (long)dgram_pair_ctrl_set_local_addr_enable(bio, num);
        break;

    
    case BIO_CTRL_DGRAM_GET_LOCAL_ADDR_CAP: 
        ret = (long)dgram_pair_ctrl_get_local_addr_cap(bio);
        break;

    
    case BIO_CTRL_DGRAM_GET_EFFECTIVE_CAPS: 
    
    case BIO_CTRL_DGRAM_GET_CAPS: 
        ret = (long)dgram_pair_ctrl_get_caps(bio);
        break;

    
    case BIO_CTRL_DGRAM_SET_CAPS: 
        ret = (long)dgram_pair_ctrl_set_caps(bio, (uint32_t)num);
        break;

    
    case BIO_CTRL_DGRAM_GET_MTU: 
        ret = (long)dgram_pair_ctrl_get_mtu(bio);
        break;

    
    case BIO_CTRL_DGRAM_SET_MTU: 
        ret = (long)dgram_pair_ctrl_set_mtu(bio, (uint32_t)num);
        break;

    
    case BIO_CTRL_EOF: 
        ret = (long)dgram_pair_ctrl_eof(bio);
        break;

    default:
        ret = 0;
        break;
    }

    return ret;
}

static long dgram_pair_ctrl(BIO *bio, int cmd, long num, void *ptr)
{
    long ret = 1;

    switch (cmd) {
    
    case BIO_C_MAKE_BIO_PAIR: 
        ret = (long)dgram_pair_ctrl_make_bio_pair(bio, (BIO *)ptr);
        break;

    
    case BIO_C_DESTROY_BIO_PAIR: 
        dgram_pair_ctrl_destroy_bio_pair(bio);
        break;

    
    case BIO_CTRL_DGRAM_GET_EFFECTIVE_CAPS: 
        ret = (long)dgram_pair_ctrl_get_effective_caps(bio);
        break;

    default:
        ret = dgram_mem_ctrl(bio, cmd, num, ptr);
        break;
    }

    return ret;
}

int BIO_new_bio_dgram_pair(BIO **pbio1, size_t writebuf1,
                           BIO **pbio2, size_t writebuf2)
{
    int ret = 0;
    long r;
    BIO *bio1 = NULL, *bio2 = NULL;

    bio1 = BIO_new(BIO_s_dgram_pair());
    if (bio1 == NULL)
        goto err;

    bio2 = BIO_new(BIO_s_dgram_pair());
    if (bio2 == NULL)
        goto err;

    if (writebuf1 > 0) {
        r = BIO_set_write_buf_size(bio1, writebuf1);
        if (r == 0)
            goto err;
    }

    if (writebuf2 > 0) {
        r = BIO_set_write_buf_size(bio2, writebuf2);
        if (r == 0)
            goto err;
    }

    r = BIO_make_bio_pair(bio1, bio2);
    if (r == 0)
        goto err;

    ret = 1;
err:
    if (ret == 0) {
        BIO_free(bio1);
        bio1 = NULL;
        BIO_free(bio2);
        bio2 = NULL;
    }

    *pbio1 = bio1;
    *pbio2 = bio2;
    return ret;
}


static size_t dgram_pair_read_inner(struct bio_dgram_pair_st *b, uint8_t *buf, size_t sz)
{
    size_t total_read = 0;

    
    while (sz > 0) {
        uint8_t *src_buf = NULL;
        size_t src_len = 0;

        
        ring_buf_tail(&b->rbuf, &src_buf, &src_len);
        if (src_len == 0)
            break;

        if (src_len > sz)
            src_len = sz;

        if (buf != NULL)
            memcpy(buf, src_buf, src_len);

        ring_buf_pop(&b->rbuf, src_len);

        if (buf != NULL)
            buf += src_len;
        total_read  += src_len;
        sz          -= src_len;
    }

    return total_read;
}


static ossl_ssize_t dgram_pair_read_actual(BIO *bio, char *buf, size_t sz,
                                           BIO_ADDR *local, BIO_ADDR *peer,
                                           int is_multi)
{
    size_t l, trunc = 0, saved_idx, saved_count;
    struct bio_dgram_pair_st *b = bio->ptr, *readb;
    struct dgram_hdr hdr;

    if (!is_multi)
        BIO_clear_retry_flags(bio);

    if (!bio->init)
        return -BIO_R_UNINITIALIZED;

    if (!ossl_assert(b != NULL))
        return -BIO_R_TRANSFER_ERROR;

    if (is_dgram_pair(b))
        readb = b->peer->ptr;
    else
        readb = b;
    if (!ossl_assert(readb != NULL && readb->rbuf.start != NULL))
        return -BIO_R_TRANSFER_ERROR;

    if (sz > 0 && buf == NULL)
        return -BIO_R_INVALID_ARGUMENT;

    
    if (local != NULL && b->local_addr_enable == 0)
        return -BIO_R_LOCAL_ADDR_NOT_AVAILABLE;

    
    saved_idx   = readb->rbuf.idx[1];
    saved_count = readb->rbuf.count;
    l = dgram_pair_read_inner(readb, (uint8_t *)&hdr, sizeof(hdr));
    if (l == 0) {
        
        if (!is_multi)
            BIO_set_retry_read(bio);
        return -BIO_R_NON_FATAL;
    }

    if (!ossl_assert(l == sizeof(hdr)))
        
        return -BIO_R_BROKEN_PIPE;

    if (sz > hdr.len) {
        sz = hdr.len;
    } else if (sz < hdr.len) {
        
        trunc = hdr.len - sz;
        if (b->no_trunc) {
            
            readb->rbuf.idx[1] = saved_idx;
            readb->rbuf.count  = saved_count;
            return -BIO_R_NON_FATAL;
        }
    }

    l = dgram_pair_read_inner(readb, (uint8_t *)buf, sz);
    if (!ossl_assert(l == sz))
        
        return -BIO_R_TRANSFER_ERROR;

    
    if (trunc > 0 && !ossl_assert(dgram_pair_read_inner(readb, NULL, trunc) == trunc))
        
        return -BIO_R_TRANSFER_ERROR;

    if (local != NULL)
        *local = hdr.dst_addr;
    if (peer != NULL)
        *peer  = hdr.src_addr;

    return (ossl_ssize_t)l;
}


static int dgram_pair_lock_both_write(struct bio_dgram_pair_st *a,
                                      struct bio_dgram_pair_st *b)
{
    struct bio_dgram_pair_st *x, *y;

    x = (a->role == 1) ? a : b;
    y = (a->role == 1) ? b : a;

    if (!ossl_assert(a->role != b->role))
        return 0;

    if (!ossl_assert(a != b && x != y))
        return 0;

    if (CRYPTO_THREAD_write_lock(x->lock) == 0)
        return 0;

    if (CRYPTO_THREAD_write_lock(y->lock) == 0) {
        CRYPTO_THREAD_unlock(x->lock);
        return 0;
    }

    return 1;
}

static void dgram_pair_unlock_both(struct bio_dgram_pair_st *a,
                                   struct bio_dgram_pair_st *b)
{
    CRYPTO_THREAD_unlock(a->lock);
    CRYPTO_THREAD_unlock(b->lock);
}


static int dgram_pair_read(BIO *bio, char *buf, int sz_)
{
    int ret;
    ossl_ssize_t l;
    struct bio_dgram_pair_st *b = bio->ptr, *peerb;

    if (sz_ < 0) {
        ERR_raise(ERR_LIB_BIO, BIO_R_INVALID_ARGUMENT);
        return -1;
    }

    if (b->peer == NULL) {
        ERR_raise(ERR_LIB_BIO, BIO_R_BROKEN_PIPE);
        return -1;
    }

    peerb = b->peer->ptr;

    
    if (dgram_pair_lock_both_write(peerb, b) == 0) {
        ERR_raise(ERR_LIB_BIO, ERR_R_UNABLE_TO_GET_WRITE_LOCK);
        return -1;
    }

    l = dgram_pair_read_actual(bio, buf, (size_t)sz_, NULL, NULL, 0);
    if (l < 0) {
        if (l != -BIO_R_NON_FATAL)
            ERR_raise(ERR_LIB_BIO, -l);
        ret = -1;
    } else {
        ret = (int)l;
    }

    dgram_pair_unlock_both(peerb, b);
    return ret;
}


static int dgram_pair_recvmmsg(BIO *bio, BIO_MSG *msg,
                               size_t stride, size_t num_msg,
                               uint64_t flags,
                               size_t *num_processed)
{
    int ret;
    ossl_ssize_t l;
    BIO_MSG *m;
    size_t i;
    struct bio_dgram_pair_st *b = bio->ptr, *readb;

    if (num_msg == 0) {
        *num_processed = 0;
        return 1;
    }

    if (!bio->init) {
        ERR_raise(ERR_LIB_BIO, BIO_R_BROKEN_PIPE);
        *num_processed = 0;
        return 0;
    }

    if (is_dgram_pair(b))
        readb = b->peer->ptr;
    else
        readb = b;

    if (CRYPTO_THREAD_write_lock(readb->lock) == 0) {
        ERR_raise(ERR_LIB_BIO, ERR_R_UNABLE_TO_GET_WRITE_LOCK);
        *num_processed = 0;
        return 0;
    }

    for (i = 0; i < num_msg; ++i) {
        m = &BIO_MSG_N(msg, i);
        l = dgram_pair_read_actual(bio, m->data, m->data_len,
                                   m->local, m->peer, 1);
        if (l < 0) {
            *num_processed = i;
            if (i > 0) {
                ret = 1;
            } else {
                ERR_raise(ERR_LIB_BIO, -l);
                ret = 0;
            }
            goto out;
        }

        m->data_len = l;
        m->flags    = 0;
    }

    *num_processed = i;
    ret = 1;
out:
    CRYPTO_THREAD_unlock(readb->lock);
    return ret;
}


static int dgram_mem_read(BIO *bio, char *buf, int sz_)
{
    int ret;
    ossl_ssize_t l;
    struct bio_dgram_pair_st *b = bio->ptr;

    if (sz_ < 0) {
        ERR_raise(ERR_LIB_BIO, BIO_R_INVALID_ARGUMENT);
        return -1;
    }

    if (CRYPTO_THREAD_write_lock(b->lock) == 0) {
        ERR_raise(ERR_LIB_BIO, ERR_R_UNABLE_TO_GET_WRITE_LOCK);
        return -1;
    }

    l = dgram_pair_read_actual(bio, buf, (size_t)sz_, NULL, NULL, 0);
    if (l < 0) {
        if (l != -BIO_R_NON_FATAL)
            ERR_raise(ERR_LIB_BIO, -l);
        ret = -1;
    } else {
        ret = (int)l;
    }

    CRYPTO_THREAD_unlock(b->lock);
    return ret;
}


static const size_t max_rbuf_size = SIZE_MAX / 2; 
static ossl_inline size_t compute_rbuf_growth(size_t target, size_t current)
{
    int err = 0;

    while (current < target) {
        if (current >= max_rbuf_size)
            return 0;

        current = safe_muldiv_size_t(current, 8, 5, &err);
        if (err)
            return 0;
        if (current >= max_rbuf_size)
            current = max_rbuf_size;
    }
    return current;
}


static size_t dgram_pair_write_inner(struct bio_dgram_pair_st *b,
                                     const uint8_t *buf, size_t sz)
{
    size_t total_written = 0;

    
    while (sz > 0) {
        size_t dst_len;
        uint8_t *dst_buf;

        
        ring_buf_head(&b->rbuf, &dst_buf, &dst_len);
        if (dst_len == 0) {
            size_t new_len;

            if (!b->grows_on_write) 
                break;
            
            new_len = compute_rbuf_growth(b->req_buf_len + sz, b->req_buf_len);
            if (new_len == 0 || !ring_buf_resize(&b->rbuf, new_len))
                break;
            b->req_buf_len = new_len;
        }

        if (dst_len > sz)
            dst_len = sz;

        memcpy(dst_buf, buf, dst_len);
        ring_buf_push(&b->rbuf, dst_len);

        buf             += dst_len;
        sz              -= dst_len;
        total_written   += dst_len;
    }

    return total_written;
}


static ossl_ssize_t dgram_pair_write_actual(BIO *bio, const char *buf, size_t sz,
                                            const BIO_ADDR *local, const BIO_ADDR *peer,
                                            int is_multi)
{
    static const BIO_ADDR zero_addr;
    size_t saved_idx, saved_count;
    struct bio_dgram_pair_st *b = bio->ptr, *readb;
    struct dgram_hdr hdr = {0};

    if (!is_multi)
        BIO_clear_retry_flags(bio);

    if (!bio->init)
        return -BIO_R_UNINITIALIZED;

    if (!ossl_assert(b != NULL && b->rbuf.start != NULL))
        return -BIO_R_TRANSFER_ERROR;

    if (sz > 0 && buf == NULL)
        return -BIO_R_INVALID_ARGUMENT;

    if (local != NULL && b->local_addr_enable == 0)
        return -BIO_R_LOCAL_ADDR_NOT_AVAILABLE;

    if (is_dgram_pair(b))
        readb = b->peer->ptr;
    else
        readb = b;
    if (peer != NULL && (readb->cap & BIO_DGRAM_CAP_HANDLES_DST_ADDR) == 0)
        return -BIO_R_PEER_ADDR_NOT_AVAILABLE;

    hdr.len = sz;
    hdr.dst_addr = (peer != NULL ? *peer : zero_addr);
    hdr.src_addr = (local != NULL ? *local : zero_addr);

    saved_idx   = b->rbuf.idx[0];
    saved_count = b->rbuf.count;
    if (dgram_pair_write_inner(b, (const uint8_t *)&hdr, sizeof(hdr)) != sizeof(hdr)
            || dgram_pair_write_inner(b, (const uint8_t *)buf, sz) != sz) {
        
        b->rbuf.idx[0] = saved_idx;
        b->rbuf.count  = saved_count;
        if (!is_multi)
            BIO_set_retry_write(bio);
        return -BIO_R_NON_FATAL;
    }

    return sz;
}


static int dgram_pair_write(BIO *bio, const char *buf, int sz_)
{
    int ret;
    ossl_ssize_t l;
    struct bio_dgram_pair_st *b = bio->ptr;

    if (sz_ < 0) {
        ERR_raise(ERR_LIB_BIO, BIO_R_INVALID_ARGUMENT);
        return -1;
    }

    if (CRYPTO_THREAD_write_lock(b->lock) == 0) {
        ERR_raise(ERR_LIB_BIO, ERR_R_UNABLE_TO_GET_WRITE_LOCK);
        return -1;
    }

    l = dgram_pair_write_actual(bio, buf, (size_t)sz_, NULL, NULL, 0);
    if (l < 0) {
        ERR_raise(ERR_LIB_BIO, -l);
        ret = -1;
    } else {
        ret = (int)l;
    }

    CRYPTO_THREAD_unlock(b->lock);
    return ret;
}


static int dgram_pair_sendmmsg(BIO *bio, BIO_MSG *msg,
                               size_t stride, size_t num_msg,
                               uint64_t flags, size_t *num_processed)
{
    ossl_ssize_t ret, l;
    BIO_MSG *m;
    size_t i;
    struct bio_dgram_pair_st *b = bio->ptr;

    if (num_msg == 0) {
        *num_processed = 0;
        return 1;
    }

    if (CRYPTO_THREAD_write_lock(b->lock) == 0) {
        ERR_raise(ERR_LIB_BIO, ERR_R_UNABLE_TO_GET_WRITE_LOCK);
        *num_processed = 0;
        return 0;
    }

    for (i = 0; i < num_msg; ++i) {
        m = &BIO_MSG_N(msg, i);
        l = dgram_pair_write_actual(bio, m->data, m->data_len,
                                    m->local, m->peer, 1);
        if (l < 0) {
            *num_processed = i;
            if (i > 0) {
                ret = 1;
            } else {
                ERR_raise(ERR_LIB_BIO, -l);
                ret = 0;
            }
            goto out;
        }

        m->flags = 0;
    }

    *num_processed = i;
    ret = 1;
out:
    CRYPTO_THREAD_unlock(b->lock);
    return ret;
}

#endif
