/*
 * Copyright (C) 2001-2005 by egnite Software GmbH
 * Copyright (c) 1993 by Digital Equipment Corporation
 * Copyright (c) 1983, 1993 by The Regents of the University of California
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS 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
 * COPYRIGHT OWNER OR 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.
 *
 * For additional information see http://www.ethernut.de/
 */

/*!
 * \file net/tcpsock.c
 * \brief TCP socket interface.
 *
 * \verbatim
 * $Id: tcpsock.c 5542 2014-01-10 17:35:18Z olereinhardt $
 * \endverbatim
 */

#include <cfg/os.h>
#include <cfg/crt.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

#include <sys/atom.h>
#include <sys/heap.h>
#include <sys/thread.h>
#include <sys/event.h>
#include <sys/timer.h>

#include <net/route.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/icmp.h>
#include <netinet/ip_icmp.h>
#include <netinet/ipcsum.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <io.h>
#include <memdebug.h>

#ifdef NUTDEBUG
#include <net/netdebug.h>
#endif

/*!
 * \addtogroup xgTcpSocket
 */
/*@{*/

TCPSOCKET *tcpSocketList = 0;   /*!< Global linked list of all TCP sockets. */

static uint16_t last_local_port; /* Unassigned local port. */

static uint_fast8_t registered;

void NutTcpDiscardBuffers(TCPSOCKET * sock)
{
    NETBUF *nb;
    while ((nb = sock->so_rx_buf) != 0) {
        sock->so_rx_buf = nb->nb_next;
        NutNetBufFree(nb);
    }
    while ((nb = sock->so_tx_nbq) != 0) {
        sock->so_tx_nbq = nb->nb_next;
        NutNetBufFree(nb);
    }
    while ((nb = sock->so_rx_nbq) != 0) {
        sock->so_rx_nbq = nb->nb_next;
        NutNetBufFree(nb);
    }
}

/*!
 * \brief Destroy a previously allocated socket.
 *
 * Release occupied socket memory.
 *
 * Applications must not call this function. It is automatically called
 * by a timer after the socket has been closed by NutTcpCloseSocket().
 *
 * \param sock Socket descriptor. This pointer must have been
 *             retrieved by calling NutTcpCreateSocket().
 */
void NutTcpDestroySocket(TCPSOCKET * sock)
{
    //@@@printf ("[%04X] Calling destroy.\n", (u_short) sock);

    /*
     * Free all memory occupied by the socket.
     */
    NutTcpDiscardBuffers(sock);
    if (sock->so_devocnt)
    {
        free(sock->so_devobuf);
        sock->so_devocnt = 0;
    }
    memset(sock, 0, sizeof(TCPSOCKET));
    free(sock);
}

/*!
 * \brief Find a matching socket.
 *
 * Loop through all sockets and find a matching connection (preferred)
 * or a listening socket.
 *
 * Applications typically do not call this function.
 *
 * \param lport Local port number.
 * \param rport Remote port number.
 * \param raddr Remote IP address in network byte order.
 *
 * \return Socket descriptor.
 */
TCPSOCKET *NutTcpFindSocket(uint16_t lport, uint16_t rport, uint32_t raddr)
{
    TCPSOCKET *sp;
    TCPSOCKET *sock = 0;

    /*
     * Try to find an exact match for the remote
     * address and port first.
     */
    for (sp = tcpSocketList; sp; sp = sp->so_next) {
        if (sp->so_local_port == lport) {
            if (sp->so_remote_addr == raddr && sp->so_remote_port == rport && sp->so_state != TCPS_CLOSED && sp->so_state != TCPS_DESTROY) {
                sock = sp;
                break;
            }
        }
    }

    /*
     * If no exact match exists, try a listening socket.
     * This part had been totally wrong, because it
     * didn't check the local port number and accepted
     * incoming requests on any port. Thanks to
     * Alejandro Lopez, who pointed this out.
     */
    if (sock == 0) {
        for (sp = tcpSocketList; sp; sp = sp->so_next) {
            if (sp->so_state == TCPS_LISTEN && sp->so_local_port == lport) {
                sock = sp;
                break;
            }
        }
    }

    return sock;
}


/*!
 * \brief Create a TCP socket.
 *
 * Allocates a TCPSOCKET structure from heap memory, initializes
 * it and returns a pointer to that structure.
 *
 * The very first call will also start the TCP state machine,
 * which is running in a separate thread.
 *
 * \return Socket descriptor of the newly created TCP socket or
 *         0 if there is not enough memory left.
 */
TCPSOCKET *NutTcpCreateSocket(void)
{
    TCPSOCKET *sock = 0;

    if (!registered) {
        if (NutRegisterIpHandler(IPPROTO_TCP, NutTcpInput) || NutTcpInitStateMachine()) {
            return NULL;
        }
        registered = 1;
    }
    if ((sock = calloc(1, sizeof(TCPSOCKET))) != 0) {
        sock->so_state = TCPS_CLOSED;

        /*
         * Initialize the virtual device interface.
         */
        sock->so_devtype = IFTYP_TCPSOCK;
        sock->so_devread = NutTcpDeviceRead;
        sock->so_devwrite = NutTcpDeviceWrite;
#ifdef __HARVARD_ARCH__
        sock->so_devwrite_P = NutTcpDeviceWrite_P;
#endif
        sock->so_devioctl = NutTcpDeviceIOCtl;
        sock->so_devselect = NutTcpDeviceSelect;

        sock->so_tx_isn = NutGetTickCount();  /* Generate the ISN from the nut_ticks counter */
        sock->so_tx_una = sock->so_tx_isn;
        sock->so_tx_nxt = sock->so_tx_isn;
        sock->so_rx_bsz = sock->so_rx_win = TCP_WINSIZE;

        sock->so_mss = TCP_MSS;
        sock->so_rtto = 1000; /* Initial retransmission time out */

        sock->so_next = tcpSocketList;

        sock->so_devobsz = TCP_MSS; /* Default output buffer size is TCP_MSS bytes */

        tcpSocketList = sock;
    }
    return sock;
}

/*!
 * \brief Set value of a TCP socket option.
 *
 * The following values can be set:
 *
 * - #TCP_MAXSEG  Maximum segment size (#uint16_t). Can only be set if
                  socket is not yet connected.
 * - #SO_SNDTIMEO Socket send timeout (#uint32_t).
 * - #SO_RCVTIMEO Socket receive timeout (#uint32_t).
 * - #SO_SNDBUF   Socket output buffer size (#uint16_t).
 *
 * \param sock    Socket descriptor. This pointer must have been
 *                retrieved by calling NutTcpCreateSocket().
 * \param optname Option to set.
 * \param optval  Pointer to the value.
 * \param optlen  Length of the value.
 * \return 0 on success, -1 otherwise. The specific error code
 *         can be retrieved by calling NutTcpError().
 */
int NutTcpSetSockOpt(TCPSOCKET * sock, int optname, const void *optval, int optlen)
{
    int rc = -1;

    if (sock == 0)
        return -1;
    switch (optname) {

    case TCP_MAXSEG:
        if (optval == 0 || optlen != sizeof(uint16_t))
            sock->so_last_error = EINVAL;
        else if (sock->so_state != TCPS_CLOSED)
            sock->so_last_error = EISCONN;
        else {
            sock->so_mss = *((uint16_t *) optval);
            rc = 0;
        }
        break;

    case SO_RCVBUF:
        if (optval == 0 || optlen != sizeof(uint16_t))
            sock->so_last_error = EINVAL;
        else {
            sock->so_rx_bsz = *((uint16_t *) optval);
            sock->so_rx_win = sock->so_rx_bsz;
            rc = 0;
        }
        break;

    case SO_SNDTIMEO:
        if (optval == 0 || optlen != sizeof(uint32_t))
            sock->so_last_error = EINVAL;
        else {
            sock->so_write_to = *((uint32_t *) optval);
            rc = 0;
        }
        break;

    case SO_RCVTIMEO:
        if (optval == 0 || optlen != sizeof(uint32_t))
            sock->so_last_error = EINVAL;
        else {
            sock->so_read_to = *((uint32_t *) optval);
            rc = 0;
        }
        break;

    case SO_SNDBUF:
        if (optval == 0 || optlen != sizeof(uint16_t))
            sock->so_last_error = EINVAL;
        else {
            NutTcpDeviceWrite(sock, 0, 0);
            sock->so_devobsz = *((uint16_t *) optval);
            rc = 0;
        }
        break;

    default:
        sock->so_last_error = ENOPROTOOPT;
        break;
    }
    return rc;
}

/*!
 * \brief Get a TCP socket option value.
 *
 * The following values can be set:
 *
 * - #TCP_MAXSEG  Maximum segment size (#uint16_t).
 * - #SO_SNDTIMEO Socket send timeout (#uint32_t).
 * - #SO_RCVTIMEO Socket receive timeout (#uint32_t).
 * - #SO_SNDBUF   Socket output buffer size (#uint16_t).
 *
 * \param sock    Socket descriptor. This pointer must have been
 *                retrieved by calling NutTcpCreateSocket().
 * \param optname Option to get.
 * \param optval  Points to a buffer receiving the value.
 * \param optlen  Length of the value buffer.
 *
 * \return 0 on success, -1 otherwise. The specific error code
 *         can be retrieved by calling NutTcpError().
 */
int NutTcpGetSockOpt(TCPSOCKET * sock, int optname, void *optval, int optlen)
{
    int rc = -1;

    if (sock == 0)
        return -1;
    switch (optname) {

    case TCP_MAXSEG:
        if (optval == 0 || optlen != sizeof(uint16_t))
            sock->so_last_error = EINVAL;
        else {
            *((uint16_t *) optval) = sock->so_mss;
            rc = 0;
        }
        break;

    case SO_RCVBUF:
        if (optval == 0 || optlen != sizeof(uint16_t))
            sock->so_last_error = EINVAL;
        else {
            *((uint16_t *) optval) = sock->so_rx_bsz;
            rc = 0;
        }
        break;

    case SO_SNDTIMEO:
        if (optval == 0 || optlen != sizeof(uint32_t))
            sock->so_last_error = EINVAL;
        else {
            *((uint32_t *) optval) = sock->so_write_to;
            rc = 0;
        }
        break;

    case SO_RCVTIMEO:
        if (optval == 0 || optlen != sizeof(uint32_t))
            sock->so_last_error = EINVAL;
        else {
            *((uint32_t *) optval) = sock->so_read_to;
            rc = 0;
        }
        break;

    case SO_SNDBUF:
        if (optval == 0 || optlen != sizeof(uint16_t))
            sock->so_last_error = EINVAL;
        else {
            *((uint16_t *) optval) = sock->so_devobsz;
            rc = 0;
        }
        break;
    default:
        sock->so_last_error = ENOPROTOOPT;
        break;
    }
    return rc;
}

/*!
 * \brief Connect to a remote socket.
 *
 * This function tries to establish a connection to the specified
 * remote port of the specified remote server. The calling thread
 * will be suspended until a connection is successfully established
 * or an error occurs.
 *
 * If a write timeout is set for this socket, the function will return 
 * an error, if the connection could not be established before the 
 * timeout. In this case the last error is set to ETIMEOUT. 
 *
 * This function is typically used by TCP client applications.
 *
 * \param sock Socket descriptor. This pointer must have been
 *             retrieved by calling NutTcpCreateSocket().
 * \param addr IP address of the host to connect (network byte order).
 * \param port Port number to connect (host byte order).
 *
 * \return 0 on success, -1 otherwise. The specific error code
 *         can be retrieved by calling NutTcpError().
 */
int NutTcpConnect(TCPSOCKET * sock, uint32_t addr, uint16_t port)
{
    TCPSOCKET *sp;
    NUTDEVICE *dev;
    uint16_t   ticks;

    if (sock == 0)
        return -1;
    /*
     * Despite RFC793 we do not allow a passive
     * open to become active.
     */
    if (sock->so_state == TCPS_LISTEN) {
        sock->so_last_error = EOPNOTSUPP;
        return -1;
    } else if (sock->so_state != TCPS_CLOSED) {
        sock->so_last_error = EISCONN;
        return -1;
    }

    /*
     * Find an unused local port.
     */
    do {
        /* Each time a new socket is created the local port number in incremented
           by a more or less randomized value between 1 and 15 (the lowest 4 bit of
           NutGetMillis() | 1). The highest two bits are always set to 1.
           This way a port range of 49152 to 65535 is used according to the IANA
           suggestions for ephemeral port usage.
         */
        ticks = (uint16_t) NutGetMillis();
        if (last_local_port) {
            last_local_port += (uint16_t) ((ticks & 0x000F) | 1);
        } else {
            last_local_port = ticks;
        }

        last_local_port |= 0xC000;

        sp = tcpSocketList;
        while (sp) {
            /* Thanks to Ralph Mason for fixing the byte order bug. */
            if (sp->so_local_port == htons(last_local_port))
                break;
            sp = sp->so_next;
        }
    } while (sp);

    /*
     * OK - we've got a new port. Now fill
     * remaining parts of the socket structure.
     */
    sock->so_local_port = htons(last_local_port);
    sock->so_remote_port = htons(port);
    sock->so_remote_addr = addr;

    /*
     * Get local address for this destination.
     */
    if ((dev = NutIpRouteQuery(addr, 0)) != 0) {
        IFNET *nif = dev->dev_icb;
        sock->so_local_addr = nif->if_local_ip;
    } else {
        sock->so_last_error = EHOSTUNREACH;
        return -1;
    }

    /*
     * Trigger active open event for the state machine.
     * This will suspend the thread until synchronized.
     */
    return NutTcpStateActiveOpenEvent(sock);
}

/*!
 * \brief Wait for incoming connect from a remote socket.
 *
 * The calling thread will be suspended until until an
 * incoming connection request is received.
 *
 * This function is typically used by TCP server applications.
 *
 * \param sock Socket descriptor. This pointer must have been
 *             retrieved by calling NutTcpCreateSocket().
 * \param port Port number to listen to (host byte order).
 *
 * \return 0 on success, -1 otherwise. The specific error code
 *         can be retrieved by calling NutTcpError().
 */
int NutTcpAccept(TCPSOCKET * sock, uint16_t port)
{
    sock->so_local_port = htons(port);

    return NutTcpStatePassiveOpenEvent(sock);
}

/*!
 * \brief Send data on a connected TCP socket.
 *
 * \param sock Socket descriptor. This pointer must have been
 *             retrieved by calling NutTcpCreateSocket(). In
 *             addition a connection must have been established
 *             by calling NutTcpConnect or NutTcpAccept.
 * \param data Pointer to a buffer containing the data to send.
 * \param len  Number of bytes to be sent.
 *
 * \return If successful, the number of bytes added to the socket transmit
 *         buffer. This is limited to the maximum segment size of the
 *         connection and thus may be less than the specified number of
 *         bytes to send. The return value -1 indicates a fatal error.
 *         On time out, a value of 0 is returned.
 */
int NutTcpSend(TCPSOCKET * sock, const void *data, int len)
{
    uint16_t unacked;

    /*
     * Check parameters.
     */
    NutThreadYield();

    if (sock == 0)
        return -1;
    if (data == 0 || len == 0)
        return 0;

    /*
     * Limit the transmission size to our maximum segment size.
     */
    if (len > sock->so_mss)
        len = sock->so_mss;

    for (;;) {
        /*
         * We can only send on an established connection.
         */
        if (sock->so_state != TCPS_ESTABLISHED) {
            sock->so_last_error = ENOTCONN;
            return -1;
        }

        /*
         * Limit the size of unacknowledged data to four full segments.
         * Also wait for peer's window open wide enough to take all our
         * data. This also avoids silly window syndrome on our side.
         */
        unacked = sock->so_tx_nxt - sock->so_tx_una;
        if ((unacked >> 2) < sock->so_mss && len <= sock->so_tx_win - unacked) {
            break;
        }
        if (NutEventWait(&sock->so_tx_tq, sock->so_write_to)) {
            return 0;
        }
    }
    /*
     * The segment will be automatically retransmitted if not
     * acknowledged in time. If this returns an error, it's a
     * fatal one.
     */
    sock->so_tx_flags |= SO_ACK;
    if (NutTcpOutput(sock, data, len))
        return -1;
    return len;
}

/*!
 * \brief Receive data on a connected TCP socket.
 *
 * \param sock Socket descriptor. This pointer must have been
 *             retrieved by calling NutTcpCreateSocket(). In
 *             addition a connection must have been established
 *             by calling NutTcpConnect or NutTcpAccept.
 * \param data Pointer to the buffer that receives the data.
 * \param size Size of the buffer.
 *
 * \return If successful, the number of received data bytes
 *         is returned. This may be less than the specified
 *         size of the buffer. The return value 0 indicates
 *         a timeout, while -1 is returned in case of an error
 *         or broken connection. Call NutTcpError() to determine
 *         the specific error code.
 */
int NutTcpReceive(TCPSOCKET * sock, void *data, int size)
{
    int i;

    NutThreadYield();
    /*
     * Check parameters.
     */
    if (sock == 0)
        return -1;
    if (sock->so_state != TCPS_ESTABLISHED && sock->so_state != TCPS_CLOSE_WAIT) {
        sock->so_last_error = ENOTCONN;
        return -1;
    }
    if (data == 0 || size == 0)
        return 0;

    /*
     * Wait until any data arrived, a timeout occurs
     * or the connection terminates.
     */
    while (sock->so_rx_cnt - sock->so_rd_cnt == 0) {
        if (sock->so_state != TCPS_ESTABLISHED) {
            sock->so_last_error = ENOTCONN;
            return -1;
        }
        if (NutEventWait(&sock->so_rx_tq, sock->so_read_to))
            return 0;
    }

    if (size > sock->so_rx_cnt - sock->so_rd_cnt)
        size = sock->so_rx_cnt - sock->so_rd_cnt;
    if (size) {
        NETBUF *nb;
        uint16_t rd_cnt;         /* Bytes read from NETBUF. */
        uint16_t nb_cnt;         /* Bytes left in NETBUF. */
        uint16_t ab_cnt;         /* Total bytes in app buffer. */
        uint16_t mv_cnt;         /* Bytes to move to app buffer. */

        rd_cnt = sock->so_rd_cnt;

        ab_cnt = 0;
        while (ab_cnt < size) {
            nb = sock->so_rx_buf;
            nb_cnt = nb->nb_ap.sz - rd_cnt;
            mv_cnt = size - ab_cnt;
            if (mv_cnt > nb_cnt)
                mv_cnt = nb_cnt;
            memcpy((char *) data + ab_cnt, (char *) (nb->nb_ap.vp) + rd_cnt, mv_cnt);
            ab_cnt += mv_cnt;
            rd_cnt += mv_cnt;
            if (mv_cnt >= nb_cnt) {
                sock->so_rx_buf = nb->nb_next;
                sock->so_rx_cnt -= rd_cnt;
                NutNetBufFree(nb);
                sock->so_rx_apc--;
                nb = sock->so_rx_buf;
                rd_cnt = 0;
            }
        }
        sock->so_rd_cnt = rd_cnt;

        /*
         * Update our receive window.
         */
        if (sock->so_state == TCPS_ESTABLISHED) {
            i = sock->so_rx_win;
            if ((i += size) > sock->so_rx_bsz)
                i = sock->so_rx_bsz;

            if (sock->so_rx_win <= sock->so_mss && i > sock->so_mss) {
                sock->so_rx_win = i;
                NutTcpStateWindowEvent(sock);
            } else {
                sock->so_rx_win = i;
            }
        }
    }
    return size;
}

/*!
 * \brief Close TCP socket.
 *
 * Note, that the socket may not be immediately destroyed
 * after calling this function. However, the application
 * must not use the socket after this call.
 *
 * \param sock Socket descriptor. This pointer must have been
 *             retrieved by calling NutTcpCreateSocket().
 *
 * \return 0 on success, -1 otherwise.
 */
int NutTcpCloseSocket(TCPSOCKET * sock)
{
    /* Flush buffer first */
    //@@@printf ("[%04X] Calling close\n", (u_short) sock);
    NutTcpDeviceWrite(sock, 0, 0);
    return NutTcpStateCloseEvent(sock);
}

/*!
 * \brief Return specific code of the last error.
 *
 * Possible error codes (net/errno.h) are:
 *
 * - EWOULDBLOCK: Operation would block
 * - EINPROGRESS: Operation now in progress
 * - EALREADY: Operation already in progress
 * - ENOTSOCK: Socket operation on non-socket
 * - EDESTADDRREQ: Destination address required
 * - EMSGSIZE: Message too long
 * - EPROTOTYPE: Protocol wrong type for socket
 * - ENOPROTOOPT: Protocol not available
 * - EPROTONOSUPPORT: Protocol not supported
 * - ESOCKTNOSUPPORT: Socket type not supported
 * - EOPNOTSUPP: Operation not supported on socket
 * - EPFNOSUPPORT: Protocol family not supported
 * - EAFNOSUPPORT: Address family not supported by protocol family
 * - EADDRINUSE: Address already in use
 * - EADDRNOTAVAIL: Can't assign requested address
 * - ENETDOWN: Network is down
 * - ENETUNREACH: Network is unreachable
 * - ENETRESET: Network dropped connection on reset
 * - ECONNABORTED: Software caused connection abort
 * - ECONNRESET: Connection reset by peer
 * - ENOBUFS: No buffer space available
 * - EISCONN: Socket is already connected
 * - ENOTCONN: Socket is not connected
 * - ESHUTDOWN: Can't send after socket shutdown
 * - ETOOMANYREFS: Too many references: can't splice
 * - ETIMEDOUT: Connection timed out
 * - ECONNREFUSED: Connection refused
 * - ELOOP: Too many levels of symbolic links
 * - ENAMETOOLONG: File name too long
 * - EHOSTDOWN: Host is down
 * - EHOSTUNREACH: No route to host
 * - ENOTEMPTY: Directory not empty
 *
 * \param sock Socket descriptor. This pointer must have been
 *             retrieved by calling NutTcpCreateSocket().
 *
 * \note Applications must not call this function to retrieve the
 *       error code if NutTcpCloseSocket() or NutTcpDestroySocket()
 *       failed.
 *
 * \todo Not all error codes are properly set right now. Some socket
 *       functions return an error without setting an error code.
 */
int NutTcpError(TCPSOCKET * sock)
{
    if (sock == 0)
        return ENOTSOCK;
    return sock->so_last_error;
}

/*!
 * \brief Read from virtual socket device.
 *
 * TCP sockets can be used like other Nut/OS devices. This routine
 * is part of the virtual socket device driver.
 *
 * This function is called by the low level input routines of the
 * \ref xrCrtLowio "C runtime library", using the _NUTDEVICE::dev_read
 * entry.
 *
 * \param sock   Socket descriptor. This pointer must have been
 *               retrieved by calling NutTcpCreateSocket().
 * \param buffer Pointer to the buffer that receives the data.
 * \param size   Maximum number of bytes to read.
 *
 * \return The number of bytes read, which may be less than the number
 *         of bytes specified. A return value of -1 indicates an error,
 *         while zero is returned in case of a timeout.
 */
int NutTcpDeviceRead(TCPSOCKET * sock, void *buffer, int size)
{
    return NutTcpReceive(sock, buffer, size);
}

static int SendBuffer(TCPSOCKET * sock, const void *buffer, int size)
{
    int rc;
    int bite;

    for (rc = 0; rc < size; rc += bite) {
        if ((bite = NutTcpSend(sock, (uint8_t *) buffer + rc, size - rc)) <= 0) {
            return -1;
        }
    }
    return rc;
}

/*!
 * \brief Write to a socket.
 *
 * TCP sockets can be used like other Nut/OS devices. This routine
 * is part of the virtual socket device driver.
 *
 * This function is called by the low level output routines of the
 * \ref xrCrtLowio "C runtime library", using the
 * \ref _NUTDEVICE::dev_write entry.
 *
 * In contrast to NutTcpSend() this routine provides some buffering.
 *
 * \param sock Socket descriptor. This pointer must have been
 *             retrieved by calling NutTcpCreateSocket().
 * \param buf  Pointer to the data to be written.
 * \param size Number of bytes to write. If zero, then the output buffer
 *             will be flushed.
 *
 * \return The number of bytes written. A return value of -1 indicates
 *         an error.
 *
 */
int NutTcpDeviceWrite(TCPSOCKET * sock, const void *buf, int size)
{
    int rc;
    uint16_t sz;
    /* hack alert for ICCAVR */
    uint8_t *buffer = (uint8_t*) buf;

    /*
     * Check parameters.
     */
    if (sock == 0)
        return -1;
    if (sock->so_state != TCPS_ESTABLISHED) {
        sock->so_last_error = ENOTCONN;
        return -1;
    }

    /* Flush buffer? */
    if (size == 0) {
        if (sock->so_devocnt) {
            if (SendBuffer(sock, sock->so_devobuf, sock->so_devocnt) < 0) {
                free(sock->so_devobuf);
                sock->so_devocnt = 0;
                return -1;
            }
            free(sock->so_devobuf);
            sock->so_devocnt = 0;
        }
        return 0;
    }

    /* If we don't have a buffer so far... */
    if (sock->so_devocnt == 0) {
        /* If new data block is bigger or equal than buffer size
         * send first part of data to NIC and store remaining
         * bytes in buffer
         */
        if ((uint16_t) size >= sock->so_devobsz) {
            rc = size % sock->so_devobsz;
            if (SendBuffer(sock, buffer, size - rc) < 0)
                return -1;
            buffer += size - rc;
        } else
            rc = size;

        /* If there are some remainings bytes, allocate buffer
         * and store them
         */
        if (rc) {
            if (!(sock->so_devobuf = malloc(sock->so_devobsz)))
                return -1;
            memcpy(sock->so_devobuf, buffer, rc);
            sock->so_devocnt = rc;
        }
        return size;
    }

    /* Check if new data fully fits in output buffer */
    if (sock->so_devocnt + size < sock->so_devobsz) {
        memcpy(sock->so_devobuf + sock->so_devocnt, buffer, size);
        sock->so_devocnt += size;
        return size;
    }

    /* Otherwise store first bytes of new data in buffer and flush
     * the buffer
     */
    sz = sock->so_devobsz - sock->so_devocnt;
    memcpy(sock->so_devobuf + sock->so_devocnt, buffer, sz);
    buffer += sz;
    if (SendBuffer(sock, sock->so_devobuf, sock->so_devobsz) < 0) {
        free(sock->so_devobuf);
        sock->so_devocnt = 0;
        return -1;
    }

    /* If remaining data is bigger or equal than buffer size
     * send first part of data to NIC and later store remaining
     * bytes in buffer
     */
    sz = size - sz;
    if (sz >= sock->so_devobsz) {
        rc = sz % sock->so_devobsz;
        if (SendBuffer(sock, buffer, sz - rc) < 0) {
            free(sock->so_devobuf);
            sock->so_devocnt = 0;
            return -1;
        }
        buffer += sz - rc;
    } else
        rc = sz;

    /* If there are some remaining bytes, store them in buffer
     */
    if (rc)
        memcpy(sock->so_devobuf, buffer, rc);
    else                        /* Otherwise free buffer */
        free(sock->so_devobuf);
    sock->so_devocnt = rc;

    return size;
}

/*!
 * \brief Write to device.
 *
 * This function is implemented for CPUs with Harvard Architecture
 * only.
 *
 * TCP sockets can be used like other Nut/OS devices. This routine
 * is part of the virtual socket device driver and similar to
 * NutTcpDeviceWrite() except that the data is located in program
 * memory.
 *
 * This function is called by the low level output routines of the
 * \ref xrCrtLowio "C runtime library", using the
 * \ref _NUTDEVICE::dev_write_P entry.
 *
 * \param sock   Socket descriptor. This pointer must have been
 *               retrieved by calling NutTcpCreateSocket().
 * \param buffer Pointer to the data in program space to be written.
 * \param size   Number of bytes to write.
 *
 * \warning Inefficient implementation. No buffering has been
 *          implemented. Thus, each call will result in a separate
 *          TCP segment.
 */
#ifdef __HARVARD_ARCH__
int NutTcpDeviceWrite_P(TCPSOCKET * sock, PGM_P buffer, int size)
{
    int rc;
    char *rp = 0;

    /*
     * Hack alert. Neither do we handle out of memory correctly
     * nor do we pass the PGM pointer to lower levels.
     */
    if (size && (rp = NutHeapAlloc(size)) != 0)
        memcpy_P(rp, buffer, size);
    rc = NutTcpDeviceWrite(sock, rp, size);
    if (rp)
        NutHeapFree(rp);

    return rc;
}
#endif

/*!
 * \brief Driver control function.
 *
 * Used by the virtual device driver to modify or query device specific
 * settings.
 *
 * \param sock  Socket descriptor. This pointer must have been
 *              retrieved by calling NutTcpCreateSocket().
 * \param cmd   Requested control function. May be set to one of the
 *              following constants:
 *              - \ref IOCTL_GETFILESIZE
 *              - \ref IOCTL_GETINBUFCOUNT
 *              - \ref IOCTL_GETOUTBUFCOUNT
 *
 * \param param Points to a buffer that contains any data required for
 *              the given control function or receives data from that
 *              function.
 * \return 0 on success, -1 otherwise.
 */
int NutTcpDeviceIOCtl(TCPSOCKET * sock, int cmd, void *param)
{
    uint32_t *lvp = (uint32_t *) param;
    int rc = 0;

    switch (cmd) {
    case IOCTL_GETFILESIZE:
    case IOCTL_GETINBUFCOUNT:
        *lvp = (sock->so_rx_cnt - sock->so_rd_cnt);
        break;
    case IOCTL_GETOUTBUFCOUNT:
        *lvp = (sock->so_devocnt);
        break;
    default:
        rc = -1;
    }

    return rc;
}

#ifndef CRT_DISABLE_SELECT_POLL
/*!
 * \brief Callback function called by select routine to check, if read 
 *        or write to this socket would block
 * \internal
 *
 * This function is called by select_scan routine of the C runtime library
 * as part of the select() implementation. If select if currently waiting
 * on a waitqueue, this function will be called to check the current state
 * as soon as the select waitqueues got signalled by the tcp statemachine
 *
 * \param sock  Socket descriptor. This pointer must have been
 *              retrieved by calling NutTcpCreateSocket().
 * \param flags Flags representing what we are waiting for (read / write / exception)
 * \param wq    Waitqueue, which should be added to the drivers waitqueue list. The 
 *              thread that called select is waiting on this waitqueue
 * \param cmd   Internal command, that is passed to NutSelectManageWq
 *
 * \return Flags representing the current filedescriptor state
 */
int NutTcpDeviceSelect (TCPSOCKET * sock, int flags, HANDLE *wq, select_cmd_t cmd)
{
    int rflags = 0;

    /* Manage the wait queue lists for the select call */
    NutSelectManageWq(&sock->so_rx_wq_list, wq, flags & WQ_FLAG_READ, cmd);
    NutSelectManageWq(&sock->so_tx_wq_list, wq, flags & WQ_FLAG_WRITE, cmd);

    if (sock->so_state == TCPS_ESTABLISHED) {
        /* Check if there is some data available for reading (compare to NutTcpReceive) */
        if (sock->so_rx_cnt - sock->so_rd_cnt > 0) {
            rflags |= WQ_FLAG_READ;
        }

        /* Check if there is some free space in the output buffer. */
        if (sock->so_devocnt < sock->so_devobsz) {
            rflags |= WQ_FLAG_WRITE;
        }
    }
    if ((sock->so_state == TCPS_CLOSED) || (sock->so_state == TCPS_CLOSE_WAIT) || (sock->so_state == TCPS_DESTROY)) {
        /* The socket has been closed. Signal for read and write. 
           Send and receive routines will then return EOF.
         */
        rflags |= WQ_FLAG_READ | WQ_FLAG_WRITE;
    }

    rflags &= flags;

    return rflags;
}
#endif
/*@}*/
