lwip_sock_tcp.c 10.6 KB
/*
 * Copyright (C) 2016 Freie Universität Berlin
 *
 * This file is subject to the terms and conditions of the GNU Lesser
 * General Public License v2.1. See the file LICENSE in the top level
 * directory for more details.
 */

/**
 * @{
 *
 * @file
 * @author  Martine Lenders <m.lenders@fu-berlin.de>
 */

#include "mutex.h"

#include "net/sock/tcp.h"
#include "timex.h"

#include "lwip/sock_internal.h"
#include "lwip/api.h"
#include "lwip/opt.h"

static inline void _tcp_sock_init(sock_tcp_t *sock, struct netconn *conn,
                                  sock_tcp_queue_t *queue)
{
    mutex_init(&sock->mutex);
    mutex_lock(&sock->mutex);
    sock->conn = conn;
    sock->queue = queue;
    sock->last_buf = NULL;
    sock->last_offset = 0;
    mutex_unlock(&sock->mutex);
}

int sock_tcp_connect(sock_tcp_t *sock, const sock_tcp_ep_t *remote,
                     uint16_t local_port, uint16_t flags)
{
    assert(sock != NULL);
    assert((remote != NULL) && (remote->port != 0));

    int res;
    struct netconn *tmp = NULL;
    struct _sock_tl_ep local = { .family = remote->family,
                                 .netif = remote->netif,
                                 .port = local_port };

    if ((res = lwip_sock_create(&tmp, &local, (struct _sock_tl_ep *)remote, 0,
                                flags, NETCONN_TCP)) == 0) {
        _tcp_sock_init(sock, tmp, NULL);
    }
    return res;
}

int sock_tcp_listen(sock_tcp_queue_t *queue, const sock_tcp_ep_t *local,
                    sock_tcp_t *queue_array, unsigned queue_len,
                    uint16_t flags)
{
    assert(queue != NULL);
    assert((local != NULL) && (local->port != 0));
    assert((queue_array != NULL) && (queue_len != 0));

    int res;
    struct netconn *tmp = NULL;

    if (queue_len > USHRT_MAX) {
        return -EFAULT;
    }
    if ((res = lwip_sock_create(&tmp, (struct _sock_tl_ep *)local, NULL, 0,
                                flags, NETCONN_TCP)) < 0) {
        return res;
    }
    assert(tmp != NULL); /* just in case lwIP is trolling */
    mutex_init(&queue->mutex);
    mutex_lock(&queue->mutex);
    queue->conn = tmp;
    queue->array = queue_array;
    queue->len = queue_len;
    queue->used = 0;
    memset(queue->array, 0, sizeof(sock_tcp_t) * queue_len);
    mutex_unlock(&queue->mutex);
    switch (netconn_listen_with_backlog(queue->conn, queue->len)) {
        case ERR_OK:
            break;
        case ERR_MEM:
            return -ENOMEM;
        case ERR_USE:
            return -EADDRINUSE;
        case ERR_VAL:
            return -EINVAL;
        default:
            assert(false); /* should not happen since queue->conn is not closed
                            * and we have a TCP conn */
            break;
    }
    return 0;
}

void sock_tcp_disconnect(sock_tcp_t *sock)
{
    assert(sock != NULL);
    mutex_lock(&sock->mutex);
    if (sock->conn != NULL) {
        netconn_close(sock->conn);
        netconn_delete(sock->conn);
        sock->conn = NULL;
        /* if sock came from a sock_tcp_queue_t: since sock is a pointer in it's
         * array it is also deleted from there, but we need to decrement the used
         * counter */
        if (sock->queue != NULL) {
            assert(sock->queue->used > 0);
            sock->queue->used--;
            sock->queue = NULL;
        }
    }
    mutex_unlock(&sock->mutex);
    memset(&sock->mutex, 0, sizeof(mutex_t));
}

void sock_tcp_stop_listen(sock_tcp_queue_t *queue)
{
    assert(queue != NULL);
    mutex_lock(&queue->mutex);
    if (queue->conn != NULL) {
        netconn_close(queue->conn);
        netconn_delete(queue->conn);
        queue->conn = NULL;
        /* sever connections established through this queue */
        for (unsigned i = 0; i < queue->len; i++) {
            sock_tcp_disconnect(&queue->array[i]);
        }
        queue->array = NULL;
        queue->len = 0;
        queue->used = 0;
    }
    mutex_unlock(&queue->mutex);
    memset(&queue->mutex, 0, sizeof(mutex_t));
}

int sock_tcp_get_local(sock_tcp_t *sock, sock_tcp_ep_t *ep)
{
    int res = 0;
    assert(sock != NULL);
    mutex_lock(&sock->mutex);
    if ((sock->conn == NULL) || lwip_sock_get_addr(sock->conn,
                                                   (struct _sock_tl_ep *)ep,
                                                   1)) {
        res = -EADDRNOTAVAIL;
    }
    mutex_unlock(&sock->mutex);
    return res;
}

int sock_tcp_get_remote(sock_tcp_t *sock, sock_tcp_ep_t *ep)
{
    int res = 0;
    assert(sock != NULL);
    mutex_lock(&sock->mutex);
    if ((sock->conn == NULL) || lwip_sock_get_addr(sock->conn,
                                                   (struct _sock_tl_ep *)ep,
                                                   0)) {
        res = -ENOTCONN;
    }
    mutex_unlock(&sock->mutex);
    return res;
}

int sock_tcp_queue_get_local(sock_tcp_queue_t *queue, sock_tcp_ep_t *ep)
{
    int res = 0;

    assert(queue != NULL);
    mutex_lock(&queue->mutex);
    if ((queue->conn == NULL) || lwip_sock_get_addr(queue->conn,
                                                    (struct _sock_tl_ep *)ep,
                                                    1)) {
        res = -EADDRNOTAVAIL;
    }
    mutex_unlock(&queue->mutex);
    return res;
}

int sock_tcp_accept(sock_tcp_queue_t *queue, sock_tcp_t **sock,
                    uint32_t timeout)
{
    struct netconn *tmp = NULL;
    int res = 0;

    assert((queue != NULL) && (sock != NULL));
    if (queue->conn == NULL) {
        return -EINVAL;
    }
    if (timeout == 0) {
        if (!mutex_trylock(&queue->mutex)) {
            return -EAGAIN;
        }
    }
    else if (timeout != 0) {
        mutex_lock(&queue->mutex);
    }
    if (queue->used < queue->len) {
#if LWIP_SO_RCVTIMEO
        if ((timeout != 0) && (timeout != SOCK_NO_TIMEOUT)) {
            netconn_set_recvtimeout(queue->conn, timeout / US_PER_MS);
        }
        else
#endif
        if ((timeout == 0) && !cib_avail(&queue->conn->acceptmbox.mbox.cib)) {
            mutex_unlock(&queue->mutex);
            return -EAGAIN;
        }
        switch (netconn_accept(queue->conn, &tmp)) {
            case ERR_OK:
                for (unsigned short i = 0; i < queue->len; i++) {
                    sock_tcp_t *s = &queue->array[i];
                    if (s->conn == NULL) {
                        _tcp_sock_init(s, tmp, queue);
                        queue->used++;
                        assert(queue->used > 0);
                        *sock = s;
                        break;
                    }
                }
                break;
            case ERR_ABRT:
                res = -ECONNABORTED;
                break;
            case ERR_MEM:
                res = -ENOMEM;
                break;
#if LWIP_SO_RCVTIMEO
            case ERR_TIMEOUT:
                res = -ETIMEDOUT;
                break;
#endif
            default:
                assert(false);
                res = -1;
                break;
        }
    }
    else {
        res = -ENOMEM;
    }
#if LWIP_SO_RCVTIMEO
    netconn_set_recvtimeout(queue->conn, 0);
#endif
    mutex_unlock(&queue->mutex);
    return res;
}

ssize_t sock_tcp_read(sock_tcp_t *sock, void *data, size_t max_len,
                      uint32_t timeout)
{
    uint8_t *data_ptr = data;
    struct pbuf *buf;
    ssize_t offset = 0, res = 0;
    bool done = false;

    assert((sock != NULL) && (data != NULL) && (max_len > 0));
    if (sock->conn == NULL) {
        return -ENOTCONN;
    }
    if (timeout == 0) {
        if (!mutex_trylock(&sock->mutex)) {
            return -EAGAIN;
        }
    }
    else {
        mutex_lock(&sock->mutex);
    }
#if LWIP_SO_RCVTIMEO
    if ((timeout != 0) && (timeout != SOCK_NO_TIMEOUT)) {
        netconn_set_recvtimeout(sock->conn, timeout / US_PER_MS);
    }
    else
#endif
    if ((timeout == 0) && !cib_avail(&sock->conn->recvmbox.mbox.cib)) {
        mutex_unlock(&sock->mutex);
        return -EAGAIN;
    }
    while (!done) {
        uint16_t copylen, buf_len;
        if (sock->last_buf != NULL) {
            buf = sock->last_buf;
        }
        else {
            err_t err;
            if ((err = netconn_recv_tcp_pbuf(sock->conn, &buf)) < 0) {
                switch (err) {
                    case ERR_ABRT:
                        res = -ECONNABORTED;
                        break;
                    case ERR_CONN:
                        res = -EADDRNOTAVAIL;
                        break;
                    case ERR_RST:
                    case ERR_CLSD:
                        res = -ECONNRESET;
                        break;
                    case ERR_MEM:
                        res = -ENOMEM;
                        break;
#if LWIP_SO_RCVTIMEO
                    case ERR_TIMEOUT:
                        res = -ETIMEDOUT;
                        break;
#endif
                    default:
                        /* no applicable error */
                        res = -1;
                        break;
                }
                break;
            }
            sock->last_buf = buf;
        }
        buf_len = buf->tot_len - sock->last_offset;
        copylen = (buf_len > max_len) ? (uint16_t)max_len : buf_len;
        pbuf_copy_partial(buf, data_ptr + offset, copylen, sock->last_offset);
        offset += copylen;
        max_len -= copylen; /* should be 0 at minimum due to copylen setting above */
        if (max_len == 0) {
            done = true;
            res = offset;   /* in case offset == 0 */
        }
        /* post-process buf */
        if (buf_len > copylen) {
            /* there is still data in the buffer */
            sock->last_buf = buf;
            sock->last_offset = copylen;
        }
        else {
            sock->last_buf = NULL;
            sock->last_offset = 0;
            pbuf_free(buf);
            break;
        }
    }
    if (offset > 0) {
        res = offset;   /* we received data so return it */
    }
    /* unset flags */
#if LWIP_SO_RCVTIMEO
    netconn_set_recvtimeout(sock->conn, 0);
#endif
    netconn_set_nonblocking(sock->conn, false);
    mutex_unlock(&sock->mutex);
    return res;
}

ssize_t sock_tcp_write(sock_tcp_t *sock, const void *data, size_t len)
{
    struct netconn *conn;
    int res = 0;

    assert(sock != NULL);
    assert((len == 0) || (data != NULL)); /* (len != 0) => (data != NULL) */
    mutex_lock(&sock->mutex);
    if (sock->conn == NULL) {
        mutex_unlock(&sock->mutex);
        return -ENOTCONN;
    }
    conn = sock->conn;
    mutex_unlock(&sock->mutex); /* we won't change anything to sock here
                                   (lwip_sock_send neither, since it remote is
                                   NULL) so we can leave the mutex */
    res = lwip_sock_send(&conn, data, len, 0, NULL, NETCONN_TCP);
    return res;
}

/** @} */