Host library for controlling a WiConnect enabled Wi-Fi module.

Dependents:   wiconnect-ota_example wiconnect-web_setup_example wiconnect-test-console wiconnect-tcp_server_example ... more

WiconnectSocket.cpp

Committer:
dan_ackme
Date:
2014-10-27
Revision:
29:b6af04b77a56
Child:
40:4b4306f3d829

File content as of revision 29:b6af04b77a56:

/**
 * ACKme WiConnect Host Library is licensed under the BSD licence: 
 * 
 * Copyright (c)2014 ACKme Networks.
 * 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. The name of the author may not be used to endorse or promote products 
 * derived from this software without specific prior written permission. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 */
#include <stdarg.h>
#include "Wiconnect.h"
#include "internal/common.h"
#include "api/StringUtil.h"


#define CHECK_CONNECTED() if(!isConnected()) return WICONNECT_NOT_CONNECTED


WiconnectResult readerCallback(void *user, void *data, int maxReadSize, int *bytesRead);



/*************************************************************************************************/
WiconnectSocket::WiconnectSocket(int rxBufferLen_, void *rxBuffer_, int txBufferLen_, void *txBuffer_)
{
    wiconnect = Wiconnect::getInstance();

    memset((void*)&txBuffer, 0, sizeof(Buffer));
    memset((void*)&rxBuffer, 0, sizeof(Buffer));

    txBuffer.size = txBufferLen_;
    txBuffer.buffer = (uint8_t*)txBuffer_;

    rxBuffer.size = rxBufferLen_;
    rxBuffer.buffer = (uint8_t*)rxBuffer_;

    if(txBuffer.size > 0)
    {
        if(txBuffer_ == NULL)
        {
#ifdef WICONNECT_ENABLE_MALLOC
            wiconnect_assert(wiconnect, "Socket(), malloc not defined", wiconnect->_malloc != NULL);
            txBuffer.buffer = (uint8_t*)wiconnect->_malloc(txBufferLen_);
            wiconnect_assert(wiconnect, "Socket(), txBuffer malloc failed", txBuffer.buffer != NULL);
            txBuffer.allocated = true;
#else
            wiconnect_assert(wiconnect, "must specify buffer", 0);
#endif
        }
        txBuffer.size -= 4;
    }

    if(rxBuffer.size > 0)
    {
        if(rxBuffer_ == NULL)
        {
#ifdef WICONNECT_ENABLE_MALLOC
            wiconnect_assert(wiconnect, "Socket(), malloc not defined", wiconnect->_malloc != NULL);
            rxBuffer.buffer = (uint8_t*)wiconnect->_malloc(rxBufferLen_);
            wiconnect_assert(wiconnect, "Socket(), rxBuffer malloc failed", rxBuffer.buffer != NULL);
            rxBuffer.allocated = true;
#else
            wiconnect_assert(wiconnect, "must specify buffer", 0);
#endif
        }
        rxBuffer.size -= 4;
    }

    handle = SOCKET_INVALID_HANDLE;
    type = SOCKET_TYPE_UNKNOWN;
    remotePort = 0;
    localPort = 0;
    connected = false;
    enableAutoClose = false;
    host[0] = 0;
}


/*************************************************************************************************/
WiconnectResult WiconnectSocket::init(uint8_t handle_, SocketType type_, const char *host_, uint16_t remotePort_, uint16_t localPort_)
{
    handle = handle_;
    type = type_;
    remotePort = remotePort_;
    localPort = localPort_;
    connected = true;
    enableAutoClose = false;

    rxBuffer.ptr = rxBuffer.buffer;
    rxBuffer.bytesPending = 0;
    txBuffer.ptr = txBuffer.buffer;
    txBuffer.bytesPending = 0;

    strncpy(host, host_, sizeof(host)-1);

    return WICONNECT_SUCCESS;
}

/*************************************************************************************************/
WiconnectSocket::~WiconnectSocket()
{
    while((handle != SOCKET_INVALID_HANDLE) && (close() == WICONNECT_PROCESSING))
    {
    }

#ifdef WICONNECT_ENABLE_MALLOC
    if(txBuffer.allocated && txBuffer.size > 0)
    {
        wiconnect_assert(wiconnect, "~Socket(), free not defined", wiconnect->_free != NULL);
        wiconnect->_free(txBuffer.buffer);
    }
    if(rxBuffer.allocated && rxBuffer.size > 0)
    {
        wiconnect_assert(wiconnect, "~Socket(), free not defined", wiconnect->_free != NULL);
        wiconnect->_free(rxBuffer.buffer);
    }
#endif
}

/*************************************************************************************************/
bool WiconnectSocket::isConnected()
{
    return connected;
}

/*************************************************************************************************/
SocketType WiconnectSocket::getType()
{
    return type;
}

/*************************************************************************************************/
const char* WiconnectSocket::getHost()
{
    return host;
}

/*************************************************************************************************/
uint16_t WiconnectSocket::getLocalPort()
{
    return localPort;
}

/*************************************************************************************************/
uint16_t WiconnectSocket::getRemotePort()
{
    return remotePort;
}

/*************************************************************************************************/
uint8_t WiconnectSocket::getHandle()
{
    return handle;
}


/*************************************************************************************************/
WiconnectResult WiconnectSocket::close()
{
    WiconnectResult result;
    CHECK_CONNECTED();
    CHECK_OTHER_COMMAND_EXECUTING();

    result = wiconnect->sendCommand("close %d", handle);

    if(result != WICONNECT_PROCESSING)
    {
        connected = false;
        wiconnect->socketClosedCallback(this);
    }

    CHECK_CLEANUP_COMMAND();

    return result;
}

/*************************************************************************************************/
WiconnectResult WiconnectSocket::poll(bool *rxDataAvailablePtr, bool autoClose)
{
    WiconnectResult result;
    int32_t status;
    bool autoClosed = false;

    *rxDataAvailablePtr = false;

    CHECK_CONNECTED();

    if(rxBuffer.size > 0 && WICONNECT_IS_IDLE())
    {
        if(rxBuffer.ptr < &rxBuffer.buffer[rxBuffer.bytesPending])
        {
            *rxDataAvailablePtr = true;
            return WICONNECT_SUCCESS;
        }
        else if(read() == WICONNECT_SUCCESS && (rxBuffer.ptr < &rxBuffer.buffer[rxBuffer.bytesPending]))
        {
            *rxDataAvailablePtr = true;
            return WICONNECT_SUCCESS;
        }
    }

    CHECK_OTHER_COMMAND_EXECUTING();

    if(WICONNECT_SUCCEEDED(result, wiconnect->sendCommand("poll %d", handle)))
    {
        if(!WICONNECT_FAILED(result, wiconnect->responseToInt32(&status)))
        {
            if(status > 0)
            {
                if(status == 2 && enableAutoClose)
                {
                    autoClosed = autoClose;
                    *rxDataAvailablePtr = false;
                }
                else
                {
                    *rxDataAvailablePtr = true;
                }
            }
        }
    }

    CHECK_CLEANUP_COMMAND();

    // if we auto-closed, then block until everything is cleaned-up
    if(autoClosed)
    {
        while(WICONNECT_IS_PROCESSING(result, close()))
        {
        }
    }

    return result;
}

/*************************************************************************************************/
WiconnectResult WiconnectSocket::write(int length, bool flush)
{
    CHECK_CONNECTED();

    if( txBuffer.size == 0)
    {
        return WICONNECT_UNSUPPORTED;
    }
    else if(length > txBuffer.size)
    {
        return WICONNECT_OVERFLOW;
    }
    txBuffer.bytesPending = length;

    return flush ? flushTxBuffer() : WICONNECT_SUCCESS;
}

/*************************************************************************************************/
WiconnectResult WiconnectSocket::write(const void* buffer, int length, bool flush)
{
    WiconnectResult result = WICONNECT_SUCCESS;
    CHECK_CONNECTED();

    if(txBuffer.size > 0)
    {
        const uint8_t *src = (const uint8_t *)buffer;

        while(length > 0)
        {
            int bytesToWrite = MIN(length, txBuffer.size - txBuffer.bytesPending);
            uint8_t *dst = (uint8_t*)&txBuffer.buffer[txBuffer.bytesPending];
            memcpy(dst, src, bytesToWrite);
            txBuffer.bytesPending += bytesToWrite;
            length -= bytesToWrite;
            src += bytesToWrite;

            if((txBuffer.bytesPending >= txBuffer.size) &&
                WICONNECT_FAILED(result, flushTxBuffer()))
            {
                 break;
            }
        }

        if(flush && txBuffer.bytesPending > 0)
        {
            result = flushTxBuffer();
        }
    }
    else
    {
        if(WICONNECT_IS_IDLE())
        {
            txBuffer.ptr = (uint8_t*)buffer;
            txBuffer.bytesPending = length;
        }

        result = flushTxBuffer();
    }


    return result;
}

/*************************************************************************************************/
WiconnectResult WiconnectSocket::read(void* buffer, uint16_t maxLength, uint16_t *bytesReadPtr)
{
    WiconnectResult result;

    CHECK_CONNECTED();

    if(rxBuffer.size > 0)
    {
        uint16_t bytesToRead = 0;
        const uint16_t bufferedBytes = (&rxBuffer.buffer[rxBuffer.bytesPending] - rxBuffer.ptr);
        if(bufferedBytes > 0)
        {
            bytesToRead = MIN(bufferedBytes, maxLength);
            memcpy(buffer, rxBuffer.ptr, bytesToRead);
            rxBuffer.ptr += bytesToRead;
            *bytesReadPtr = bytesToRead;
        }
        if(rxBuffer.ptr >= &rxBuffer.buffer[rxBuffer.bytesPending])
        {
            clearRxBuffer();
        }
        if(bytesToRead > 0)
        {
            return WICONNECT_SUCCESS;
        }
    }

    CHECK_OTHER_COMMAND_EXECUTING();

    if(WICONNECT_SUCCEEDED(result, wiconnect->sendCommand((char*)buffer, maxLength, "read %d %d", handle, maxLength)))
    {
        const uint16_t bytesRead = wiconnect->getLastCommandResponseLength();
        enableAutoClose = (bytesRead == 0);
        *bytesReadPtr = bytesRead;
    }

    CHECK_CLEANUP_COMMAND();

    return result;
}

/*************************************************************************************************/
WiconnectResult WiconnectSocket::read(uint8_t **bufferPtr, uint16_t *bytesReadPtr)
{
    WiconnectResult result = WICONNECT_SUCCESS;

    CHECK_CONNECTED();

    if(rxBuffer.size == 0)
    {
        return WICONNECT_UNSUPPORTED;
    }
    else if(bufferPtr != NULL && bytesReadPtr == NULL)
    {
        return WICONNECT_BAD_ARG;
    }

    if(rxBuffer.ptr >= &rxBuffer.buffer[rxBuffer.bytesPending])
    {
        clearRxBuffer();
    }

    if(rxBuffer.bytesPending < rxBuffer.size)
    {
        const int bytesToRead = rxBuffer.size - rxBuffer.bytesPending;
        char* ptr = (char*)&rxBuffer.buffer[rxBuffer.bytesPending];

        CHECK_OTHER_COMMAND_EXECUTING();

        loop:
        if(bytesToRead > 0 && WICONNECT_SUCCEEDED(result, wiconnect->sendCommand(ptr, bytesToRead, "read %d %d", handle, bytesToRead)))
        {
            const uint16_t bytesRead = wiconnect->getLastCommandResponseLength();
            enableAutoClose = (bytesRead == 0);
            rxBuffer.bytesPending += bytesRead;
        }

        // if still processing and in non-blocking mode,
        // then this api call must block until the command completes
        if(result == WICONNECT_PROCESSING && wiconnect->nonBlocking)
        {
            goto loop;
        }

        CHECK_CLEANUP_COMMAND();
    }

    if(bufferPtr != NULL)
    {
        *bufferPtr = rxBuffer.buffer;
        *bytesReadPtr = rxBuffer.bytesPending;
        clearRxBuffer();
    }

    return result;
}

/*************************************************************************************************/
WiconnectResult WiconnectSocket::getc(uint8_t *c)
{
    WiconnectResult result;

    if(rxBuffer.size == 0)
    {
        return WICONNECT_UNSUPPORTED;
    }

    if(rxBuffer.bytesPending == 0 &&
      WICONNECT_FAILED(result, read()))
    {
        return result;
    }
    else if(rxBuffer.ptr < &rxBuffer.buffer[rxBuffer.bytesPending])
    {
        *c = *rxBuffer.ptr;
        ++rxBuffer.ptr;
        return WICONNECT_SUCCESS;
    }
    else
    {
        clearRxBuffer();
        return WICONNECT_ERROR;
    }
}

/*************************************************************************************************/
WiconnectResult WiconnectSocket::putc(uint8_t c, bool flush)
{
    WiconnectResult result = WICONNECT_SUCCESS;
    CHECK_CONNECTED();

    if(txBuffer.size == 0)
    {
        return WICONNECT_UNSUPPORTED;
    }
    else if(txBuffer.bytesPending < txBuffer.size)
    {
        uint8_t *ptr = (uint8_t*)&txBuffer.buffer[txBuffer.bytesPending];
        *ptr = c;
        ++txBuffer.bytesPending;

        if(flush || txBuffer.bytesPending >= txBuffer.size)
        {
            result = flushTxBuffer();
        }
    }
    else
    {
        result = WICONNECT_OVERFLOW;
    }

    return result;
}

/*************************************************************************************************/
WiconnectResult WiconnectSocket::puts(const char *s, bool flush)
{
    const int len = strlen(s);
    return write(s, len, flush);
}

/*************************************************************************************************/
WiconnectResult WiconnectSocket::printf(const char* format, ...)
{
    WiconnectResult result = WICONNECT_SUCCESS;

    CHECK_CONNECTED();
    if(txBuffer.size == 0)
    {
        return WICONNECT_UNSUPPORTED;
    }

    const int available = txBuffer.size - txBuffer.bytesPending;
    char *ptr = (char*)&txBuffer.buffer[txBuffer.bytesPending];
    va_list args;
    va_start(args, format);
    const int len = vsnprintf(ptr, available, format, args);
    if(len > available)
    {
        return WICONNECT_OVERFLOW;
    }
    else
    {
        txBuffer.bytesPending += len;
    }

    if(txBuffer.bytesPending >= txBuffer.size)
    {
        result = flushTxBuffer();
    }

    return result;
}


/*************************************************************************************************/
WiconnectResult WiconnectSocket::flushTxBuffer()
{
    WiconnectResult result = WICONNECT_SUCCESS;

    CHECK_CONNECTED();

    if(txBuffer.size == 0)
    {
        CHECK_OTHER_COMMAND_EXECUTING();
    }

    if(txBuffer.bytesPending > 0)
    {
        loop:
        result = wiconnect->sendCommand(ReaderFunc(readerCallback), &this->txBuffer, "write %u %u", handle, txBuffer.bytesPending);

        // if still processing and in non-blocking mode and using a txtBuffer,
        // then this api call must block until the command completes
        if(result == WICONNECT_PROCESSING && wiconnect->nonBlocking && txBuffer.size > 0)
        {
            goto loop;
        }
    }

    if(txBuffer.size == 0)
    {
        CHECK_CLEANUP_COMMAND();
    }

    if(result != WICONNECT_PROCESSING)
    {
        txBuffer.ptr = txBuffer.buffer;
        txBuffer.bytesPending = 0;
    }

    return result;
}



/*************************************************************************************************/
void WiconnectSocket::clearRxBuffer()
{
    rxBuffer.bytesPending = 0;
    rxBuffer.ptr = rxBuffer.buffer;
}

/*************************************************************************************************/
uint8_t* WiconnectSocket::getTxBuffer()
{
    return txBuffer.buffer;
}
/*************************************************************************************************/
int WiconnectSocket::getTxBufferSize()
{
    return txBuffer.size;
}
/*************************************************************************************************/
int WiconnectSocket::getTxBufferBytesPending()
{
    return txBuffer.bytesPending;
}
/*************************************************************************************************/
uint8_t* WiconnectSocket::getRxBuffer()
{
    return rxBuffer.buffer;
}
/*************************************************************************************************/
int WiconnectSocket::getRxBufferSize()
{
    return rxBuffer.size;
}
/*************************************************************************************************/
int WiconnectSocket::getRxBufferBytesPending()
{
    return rxBuffer.bytesPending;
}


/*************************************************************************************************/
WiconnectResult readerCallback(void *user, void *data, int maxReadSize, int *bytesRead)
{
    Buffer *txBuffer = (Buffer*)user;

    if(txBuffer->bytesPending == 0)
    {
        *bytesRead = EOF;
    }
    else
    {
        const int bytesToWrite = MIN(maxReadSize, txBuffer->bytesPending);
        memcpy(data, txBuffer->ptr, bytesToWrite);
        txBuffer->ptr += bytesToWrite;
        txBuffer->bytesPending -= bytesToWrite;
        *bytesRead = bytesToWrite;
    }

    return WICONNECT_SUCCESS;
}