/** \file
 *
 *  Contains the DataOverHttps class implementation.
 *
 *  Copyright (c) 2013 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

#include "DataOverHttps.h"
#include "DataOverHttpsIF.h"

#include "data/ConfigReader.h"
#include "data/Slate.h"
#include "data/UniversalDataWriter.h"
#include "io/FileInStream.h"
#include "supervisor/DataReceiver.h"
#include "supervisor/Supervisor.h"
#include "utils/Encoder.h"
#include "VehicleIF.h"
#include "Radio_SurfaceIF.h"

#include <fcntl.h>
#include <netinet/in.h>
#include <netdb.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include "io/StrIOStream.h"

using namespace DataOverHttpsIF;

const char* const DataOverHttps::KEY_TYPE_STRS[KEY_COUNT] =   //
{
    "busy", //
    "file", //
    "filename", //
    "fileSize", //
    "momsn", //
    "mtmsn", //
    "vehicle", //
};

DataOverHttps::KeyType DataOverHttps::Str2KeyType(
    const char* const keyTypeStr )
{
    for( int i = KEY_UNKNOWN + 1; i < KEY_COUNT; ++i )
    {
        if( strncmp( keyTypeStr, KEY_TYPE_STRS[i], strlen( KEY_TYPE_STRS[i] ) + 1 )
                == 0 )
        {
            return ( KeyType ) i;
        }
    }
    return KEY_UNKNOWN;
}

bool DataOverHttps::ParseDataRead( char** data, KeyType &key, char** value )
{
//SMF    if( verbosity_ > 0 ) logger_.syslog( "ParseDataRead( data = " + Str( data ) + ", key, value = " + Str( value ), verbosity_ > 2 ? Syslog::IMPORTANT : Syslog::INFO );
    key = KEY_UNKNOWN;
    if( **data != 0 )
    {
        char* eqAt = strchr( *data, '=' );
        if( NULL != eqAt )
        {
            *eqAt = 0;
            key = Str2KeyType( *data );
            *data = ++eqAt;
            if( **data != 0 )
            {
                *value = *data;
                char* andAt = strchr( *data, '&' );
                if( NULL != andAt )
                {
                    *andAt = 0;
                    *data = ++andAt; // point to next item
                }
                else
                {
                    --*data; // point at a '0'
                }
//SMF                if( verbosity_ > 0 ) logger_.syslog( "true ParseDataRead( data = " + Str( data ) + ", key, value = " + Str( value ), verbosity_ > 2 ? Syslog::IMPORTANT : Syslog::INFO );
                return true;
            }
        }
    }
//SMF    if( verbosity_ > 0 ) logger_.syslog( "false ParseDataRead( data = " + Str( data ) + ", key, value = " + Str( value ), verbosity_ > 2 ? Syslog::IMPORTANT : Syslog::INFO );
    return false;
}

const Timespan DataOverHttps::PERIOD( 1 );

DataOverHttps::DataOverHttps( const Module* module ) :
    AsyncComponent( DataOverHttpsIF::NAME, module, PERIOD ), //
    debug_( 0 ), //
    verbosity_( 0 ), //
    sendDataToShore_( false ), //
    wasConnected_( false ), //
    connectionTimeout_( 30 ), //
    period_( 60 ), //
    timeout_( 180 ), //
    connectStart_( Timestamp::NOT_SET_TIME ), //
    lastPoll_( Timestamp::NOT_SET_TIME ), //
    lastComms_( Timestamp::NOT_SET_TIME ), //
    radioSurfaceActive_( true ),
    dashSSL_( true ), //
    addrinfo_( NULL ), //
    socket_( -1 ), //
    sslHandle_( NULL ), //
    sslContext_( NULL ), //
    dataState_( TCP_CONNECT ), //
    busy_( true ), //
    outgoing_( NULL ),
    outgoingSize_( 0 ), //
    decodedSize_( 0 ), //
    bufferPos_( 0 ), //
    dataStart_( 0 ), //
    dataSize_( -1 ), //
    sendFilename_(), // Name of file currently being sent to shore
    lastMtmsn_(), // Identity of last packet received from shore
    cmdReceived_( false ) // True if a command has been received from shore since the last time power was applied
{
    // Slate outputs
    communicationsWriter_ = newUniversalWriter( UniversalURI::PLATFORM_COMMUNICATIONS, Units::BOOL, 0 );
    communicationsWriter_->setUnavailable();

    connectionStatusWriter_ = newDataWriter( DataOverHttpsIF::CONNECTION_STATUS );

    // Slate inputs
    radioSurfacePowerReader_ = newDataReader( Radio_SurfaceIF::RADIO_SURFACE_POWER );

    // Configuration
    sendDataToShoreCfgReader_ = newConfigReader( VehicleIF::SEND_DATA_TO_SHORE_CFG );
    connectionTimeoutCfgReader_ = newConfigReader( DataOverHttpsIF::CONNECTION_TIMEOUT_CFG );
    periodCfgReader_ = newConfigReader( DataOverHttpsIF::PERIOD_CFG );
    timeoutCfgReader_ = newConfigReader( DataOverHttpsIF::TIMEOUT_CFG );
    verbosityLevelCfgReader_ = newConfigReader( DataOverHttpsIF::VERBOSITY_CFG );

    persistentPause_ = true;

    // Register the error strings for libcrypto & libssl
    SSL_load_error_strings();
    // Register the available ciphers and digests
    SSL_library_init();

    loadParams(); // also reads configs


    // TODO: This isn't quite what I had in mind for verbosity -- it's more like a switch for volume. Verbosity would actually turn on different messages. Maybe there should be logVerbosity_ and logVolume_?
    if( verbosity_ > 9 ) debugLevel_ = Syslog::IMPORTANT;
    else if( verbosity_ > 1 ) debugLevel_ = Syslog::INFO;
    else if( verbosity_ == 1 ) debugLevel_ = Syslog::DEBUG;
    else if( verbosity_ < 1 ) debugLevel_ = Syslog::NONE;

    this->setFailureMissionCritical( false );
}

DataOverHttps::~DataOverHttps()
{
    if( simulateHardware() )
    {
        return;
    }
    //CONF_modules_free();
#if (OPENSSL_VERSION_NUMBER <  0x10100000)
    ERR_remove_state( 0 );
#endif
    //ENGINE_cleanup();
    //CONF_modules_unload(1);
    ERR_free_strings();
    EVP_cleanup();
    CRYPTO_cleanup_all_ex_data();
#if (OPENSSL_VERSION_NUMBER <  0x10100000)
    sk_SSL_COMP_free( SSL_COMP_get_compression_methods() );
#endif
    if( NULL != addrinfo_ )
    {
        freeaddrinfo( addrinfo_ );
    }
}

// Load parameters
bool DataOverHttps::loadParams( void )
{
    // Check if all the parameters are read correctly
    bool ok = true;
    ok &= Slate::ReadOnce( VehicleIF::DASH_IP_CFG, dashIP_, logger_ );
    ok &= Slate::ReadOnce( VehicleIF::DASH_PATH_CFG, dashPath_, logger_ );
    ok &= Slate::ReadOnce( VehicleIF::DASH_PORT_CFG, dashPort_, logger_ );
    ok &= Slate::ReadOnce( VehicleIF::DASH_SSL_CFG, Units::BOOL, dashSSL_,
                           logger_ );
    ok &= Slate::ReadOnce( VehicleIF::IMEI_CFG, imei_, logger_ );
    ok &= Slate::ReadOnce( VehicleIF::IMEI_PASSWORD_CFG, imeiPassword_, logger_ );
    ok &= Slate::ReadOnce( VehicleIF::KEY_TEXT_CFG, keyText_, logger_ );
    ok &= readConfig();

    headerStart_ = "POST " + dashPath_.asString()
                   + "/dataLink HTTP/1.1\r\n" //
                   "User-Agent: LRAUV\r\n"//
                   "Host: " + dashIP_.asString() + ":" + dashPort_.asString()
                   + "\r\n" //
                   "Accept: */*\r\n"//
                   "Content-Type: application/x-www-form-urlencoded\r\n"//
                   "Content-Length: ";

    size_t imeiPasswordLength = imeiPassword_.asString().length();
    size_t encodedSize = imeiPasswordLength * 3 + 1;
    char* encoded = new char[encodedSize];
    encodedSize = Encoder::UrlEncode(
                      imeiPassword_.asString().cStr(), imeiPasswordLength,
                      encoded, encodedSize );
    contentStart_ = "imei=" + imei_.asString() + "&imeiPassword=" + encoded;
    contentStart_ += "&gitRef=" + Str( _GIT_DESCRIBE );
    delete[] encoded;
    return ok;
}

bool DataOverHttps::readConfig()
{
    // Check if all the parameters are read correctly
    bool ok = true;
    unsigned char sendDataToShore = sendDataToShore_;
    ok &= sendDataToShoreCfgReader_->read( Units::BOOL, sendDataToShore );
    sendDataToShore_ = sendDataToShore;
    ok &= connectionTimeoutCfgReader_->read( Units::SECOND, connectionTimeout_ );
    ok &= periodCfgReader_->read( Units::SECOND, period_ );
    ok &= timeoutCfgReader_->read( Units::SECOND, timeout_ );
    ok &= verbosityLevelCfgReader_->read( Units::COUNT, verbosity_ );
    return ok;
}

void DataOverHttps::initialize()
{
    dataState_ = TCP_CONNECT;
}

void DataOverHttps::uninitialize()
{
    dataState_ = disconnect();
    wasConnected_ = false;
}

void DataOverHttps::run()
{
    if( !( dataState_ == DISCONNECTED ) )
    {
        DEBUG_LOG( "run, dataState=", ( int ) dataState_ )
    }
    readConfig();

    if( !sendDataToShore_ )
    {
        // Let someone else deal with the data...
        if( verbosity_ > 0 ) logger_.syslog( "Not sending data to shore.", Syslog::INFO );
        communicationsWriter_->setUnavailable( true );
        sendFilename_ = Str::EMPTY_STR;
        return;
    }

    if( communicationsWriter_->isDataRequested()
            && !communicationsWriter_->isUnavailable() && !simulateHardware() )
    {
        if( verbosity_ > 1 ) logger_.syslog( "platform communications requested and available", Syslog::INFO );
        if( cmdReceived_ )
        {
            cmdReceived_ = false;
            // Close and compress the current shore log
            Supervisor::IncrementShoreFile();
        }

        // Let's force a poll
        lastPoll_ = Timestamp::NOT_SET_TIME;
        // See if someone else has sent the file
        if( sendFilename_ != Str::EMPTY_STR )
        {
            if( access( sendFilename_.cStr(), F_OK ) == -1 )
            {
                // file doesn't exist
                sendFilename_ = Str::EMPTY_STR;
            }
        }
        // Let's queue up the data to send.
        if( sendFilename_ == Str::EMPTY_STR )
        {
            sendFilename_ = Supervisor::GetToShoreFilename( Service::SERVICE_COURIER );
            if( sendFilename_ == Str::EMPTY_STR )
            {
                sendFilename_ = Supervisor::GetToShoreFilename( Service::SERVICE_EXPRESS );
            }
        }
        if( sendFilename_ == Str::EMPTY_STR )
        {
            if( verbosity_ > 2 ) logger_.syslog( "Finished sending data.", Syslog::INFO );
            communicationsWriter_->write( Units::BOOL, true );
        }
        else
        {
            if( verbosity_ > 2 ) logger_.syslog( "queued: " + sendFilename_, Syslog::INFO );
        }
    }
    else if( simulateHardware() )
    {
        communicationsWriter_->write( Units::BOOL, true );
    }

    bool radioSurfaceActive( true );
    switch( dataState_ )
    {
    case DISCONNECTED:
        radioSurfaceActive = radioSurfacePowerReader_->isActive();
        if( radioSurfaceActive != radioSurfaceActive_ )
        {
            logger_.syslog( "Radio surface powered " + Str( radioSurfaceActive ? "ON." : "OFF, will not connect." ), Syslog::INFO );
            radioSurfaceActive_ = radioSurfaceActive;
        }

        if( !radioSurfaceActive_ || ( radioSurfaceActive_ && ( decodedSize_ == 0 && lastPoll_.elapsed() < period_ ) ) )
        {
            if( verbosity_ > 2 ) logger_.syslog( "data state is DISCONNECTED ", Syslog::INFO );
            break;
        }
    // No break is intentional
    case TCP_CONNECT:
        if( verbosity_ > 2 ) logger_.syslog( "data state is TCP_CONNECT", Syslog::INFO );
        lastPoll_ = Timestamp::Now();
        if( !simulateHardware() )
        {
            dataState_ = tcpConnect();
        }
        else
        {
            dataState_ = TCP_CONNECTING;
        }
        break;
    case TCP_CONNECTING:
        if( verbosity_ > 2 ) logger_.syslog( "data state is TCP_CONNECTING ", Syslog::INFO );
        if( !simulateHardware() )
        {
            dataState_ = tcpConnecting();
        }
        else
        {
            dataState_ = SSL_CONNECT;
        }
        break;
    case SSL_CONNECT:
        if( verbosity_ > 2 ) logger_.syslog( "data state is SSL_CONNECT", Syslog::INFO );
        if( !simulateHardware() )
        {
            dataState_ = sslConnect();
        }
        else
        {
            dataState_ = SSL_CONNECTING;
        }
        break;
    case SSL_CONNECTING:
        if( verbosity_ > 2 ) logger_.syslog( "data state is SSL_CONNECTING", Syslog::INFO );
        if( !simulateHardware() )
        {
            dataState_ = sslConnecting();
        }
        else
        {
            dataState_ = DATA_WRITE;
        }
        break;
    case DATA_WRITE:
        if( verbosity_ > 2 ) logger_.syslog( "data state is DATA_WRITE", Syslog::INFO );
        if( !simulateHardware() )
        {
            dataState_ = dataWrite();
        }
        else
        {
            dataState_ = DATA_WRITING;
        }
        break;
    case DATA_WRITING:
        if( verbosity_ > 2 ) logger_.syslog( "data state is DATA_WRITING", Syslog::INFO );
        if( !simulateHardware() )
        {
            dataState_ = dataWriting();
        }
        else
        {
            dataState_ = DATA_READ;
        }
        break;
    case DATA_READ:
        if( verbosity_ > 2 ) logger_.syslog( "data state is DATA_READ", Syslog::INFO );
        if( !simulateHardware() )
        {
            dataState_ = dataRead();
        }
        else
        {
            lastComms_ = Timestamp::Now();
            dataState_ = DISCONNECT;
        }
        break;
    case DISCONNECT:
        if( verbosity_ > 2 ) logger_.syslog( "data state is DISCONNECT", Syslog::INFO );
        // Simulate check in disconnect function
        dataState_ = disconnect();
        break;
    }

    if( lastComms_.elapsed() > timeout_ )
    {
        if( !communicationsWriter_->isUnavailable() )
        {
            dataState_ = disconnect();
            logger_.syslog( "setting unavailable, lastComms_.elapsed()=",
                            lastComms_.elapsed().asFloat(), Syslog::INFO );
            communicationsWriter_->setUnavailable( true );
            connectionStatusWriter_->write( Units::BOOL, false ); // set a simple bool to true in the slate
        }
        sendFilename_ = Str::EMPTY_STR;
    }
    else
    {
        if( communicationsWriter_->isUnavailable() )
        {
            logger_.syslog( "setting available, lastComms_.elapsed()=",
                            lastComms_.elapsed().asFloat(), Syslog::INFO );
            communicationsWriter_->setUnavailable( false );
            connectionStatusWriter_->write( Units::BOOL, true ); // set a simple bool to true in the slate
        }
    }

}

/// Should return [myNamespace]::SIMULATE_HARDWARE, or [myNamespace]::POWER, etc
ConfigURI DataOverHttps::getConfigURI( ConfigOption configOption ) const
{
    switch( configOption )
    {
    case CONFIG_POWER:
        return DataOverHttpsIF::POWER;
    case CONFIG_SIMULATE_HARDWARE:
        return DataOverHttpsIF::SIMULATE_HARDWARE;
    default:
        return ConfigURI::NO_CONFIG_URI;
    }
}

DataOverHttps::DataState DataOverHttps::tcpConnect()
{
    DEBUG_LOG( "tcpConnect" )
    int error;

    connectStart_ = Timestamp::Now();
    if( NULL == addrinfo_ )
    {
        struct addrinfo hints;
        memset( &hints, 0, sizeof( hints ) );
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_family = AF_INET;
        if( isdigit( dashIP_.asString().cStr()[0] ) )
        {
            logger_.syslog( "dashIP=" + dashIP_.asString()
                            + " starts with a digit so assuming it is a numeric IP", Syslog::DEBUG );
            hints.ai_flags = AI_NUMERICHOST;
        }
        else
        {
            logger_.syslog( "dashIP=" + dashIP_.asString()
                            + " does not start with a digit so assuming it is a host name", Syslog::DEBUG );
        }

        if( ( error = getaddrinfo( dashIP_.asString().cStr(),
                                   dashPort_.asString().cStr(), &hints, &addrinfo_ ) ) != 0 )
        {
            setFailure( FailureMode::HARDWARE );
            logger_.syslog(
                "Error resolving dash IP address & port: "
                + dashIP_.asString() + ":" + dashPort_.asString()
                + ". Error is " + gai_strerror( error ),
                Syslog::FAULT );
            return TCP_CONNECT;
        }
    }

    if( socket_ == -1 )
    {
        socket_ = socket( addrinfo_->ai_family, addrinfo_->ai_socktype,
                          addrinfo_->ai_protocol );
        if( socket_ == -1 )
        {
            setFailure( FailureMode::HARDWARE );
            logger_.syslog(
                "Error creating socket due to error #" + Str( errno, 10 )
                + ": " + strerror( errno ), Syslog::FAULT );
            return TCP_CONNECT;
        }
        int flags = fcntl( socket_, F_GETFL, 0 );
        fcntl( socket_, F_SETFL, flags | O_NONBLOCK );
    }

    error = connect( socket_, addrinfo_->ai_addr, addrinfo_->ai_addrlen );
    if( error == 0 )
    {
        return sslConnect();
    }
    if( error == -1 && errno != EINPROGRESS )
    {
        if( errno != ENETUNREACH )
        {
            logger_.syslog(
                "Error connecting due to error #" + Str( errno, 10 ) + ": "
                + strerror( errno ), Syslog::FAULT );
        }
        return TCP_CONNECT;
    }

    return TCP_CONNECTING;
}

DataOverHttps::DataState DataOverHttps::tcpConnecting()
{
    DEBUG_LOG( "tcpConnecting" )
    struct timeval tv;
    fd_set fdset;
    int so_error = -1;
    socklen_t len = sizeof so_error;
    tv.tv_sec = 0;             // instant timeout
    tv.tv_usec = 0;
    FD_ZERO( &fdset );
    FD_SET( socket_, &fdset );
    if( select( socket_ + 1, NULL, &fdset, NULL, &tv ) == 1 )
    {
        getsockopt( socket_, SOL_SOCKET, SO_ERROR, &so_error, &len );
        if( so_error == 0 )
        {
            return sslConnect();
        }
    }
    if( connectStart_.elapsed().asFloat() > connectionTimeout_ )
    {
        if( wasConnected_ )
        {
            logger_.syslog( "Exceeded connect timeout, disconnecting.",
                            Syslog::INFO );
        }
        else
        {
            wasConnected_ = false;
        }
        return disconnect();
    }
    return TCP_CONNECTING;
}

DataOverHttps::DataState DataOverHttps::sslConnecting()
{
    DEBUG_LOG( "sslConnecting" )
    int ssl_connect_stat = SSL_connect( sslHandle_ );
    if( ssl_connect_stat == 1 )
    {
        wasConnected_ = true;
        return dataWrite();

    }
    int ssl_err = SSL_get_error( sslHandle_, ssl_connect_stat );
    if( ssl_err != SSL_ERROR_WANT_READ && ssl_err != SSL_ERROR_WANT_WRITE )
    {
        int err;
        while( 0 != ( err = ERR_get_error() ) )
        {
            ERR_error_string( err, sslErrBuf_ );
            logger_.syslog( "SSL connect error: ", sslErrBuf_ );
        }
        return DISCONNECT;
    }

    if( connectStart_.elapsed().asFloat() > connectionTimeout_ )
    {
        return disconnect();
    }
    return SSL_CONNECTING;
}

DataOverHttps::DataState DataOverHttps::sslConnect()
{
    DEBUG_LOG( "sslConnect" )
    if( !dashSSL_ )
    {
        return dataWrite();
    }

    // New context saying we are a client, and using SSL 2 or 3
    sslContext_ = SSL_CTX_new( SSLv23_client_method() );
    if( sslContext_ == NULL )
    {
        int err;
        while( 0 != ( err = ERR_get_error() ) )
        {
            ERR_error_string( err, sslErrBuf_ );
            logger_.syslog( "NEw SSL context error: ", sslErrBuf_ );
        }
        return DISCONNECT;
    }

    // Create an SSL struct for the connection
    sslHandle_ = SSL_new( sslContext_ );
    if( sslHandle_ == NULL )
    {
        int err;
        while( 0 != ( err = ERR_get_error() ) )
        {
            ERR_error_string( err, sslErrBuf_ );
            logger_.syslog( "New SSL struct error: ", sslErrBuf_ );
        }
        return DISCONNECT;
    }

    // Connect the SSL struct to our connection
    if( !SSL_set_fd( sslHandle_, socket_ ) )
    {
        int err;
        while( 0 != ( err = ERR_get_error() ) )
        {
            ERR_error_string( err, sslErrBuf_ );
            logger_.syslog( "New SSL socket error: ", sslErrBuf_ );
        }
        return DISCONNECT;
    }

    return sslConnecting();
}

DataOverHttps::DataState DataOverHttps::dataWrite()
{
    DEBUG_LOG( "dataWrite" );

    if( verbosity_ > 0 ) logger_.syslog( "dataWrite() sendFilename_: " + Str( sendFilename_ ) + ",  busy: " + Str( busy_ ), verbosity_ > 2 ? Syslog::IMPORTANT : Syslog::INFO );

    Str content( Str::EMPTY_STR );
    if( Str::EMPTY_STR != sendFilename_ && !busy_ )
    {
        size_t sendFileSize( 0 );
        struct stat st;
        if( 0 == stat( sendFilename_.cStr(), &st ) )
        {
            sendFileSize = st.st_size;
        }
        else
        {
            logger_.syslog( "Could not stat file " + sendFilename_,
                            Syslog::IMPORTANT );
            sendFileSize = 0;
        }
        FileInStream inStream( sendFilename_.cStr() );
        size_t encodedSize( 0 );
        char* encoded = NULL;
        if( inStream.isReadable() )
        {
            char* buffer = new char[sendFileSize + 1];
            inStream.read( ( char* ) buffer, sendFileSize );
            inStream.close();
            encodedSize = sendFileSize * 3 + 1;
            encoded = new char[encodedSize];
            encodedSize = Encoder::UrlEncode( buffer, sendFileSize, encoded,
                                              encodedSize );
            delete[] buffer;
        }
        else   // Entry is null?
        {
            logger_.syslog( "Could not open file " + sendFilename_, Syslog::FAULT );
            //TODO: Need to tell supervisor to skip the file in the future
        }
        if( NULL != encoded )
        {
            content = "&filename=" + sendFilename_ + "&fileSize="
                      + Str( sendFileSize, 10 ) + "&file="
                      + Str( encoded, encodedSize );
            delete[] encoded;
            logger_.syslog( "Sending " + Str( sendFileSize, 10 ) + " bytes from file " + sendFilename_, Syslog::INFO );
        }
    }
    if( lastMtmsn_ != Str::EMPTY_STR )
    {
        Str mtmsnStr = "mtmsn=" + lastMtmsn_;
        content += "&" + mtmsnStr;
        lastMtmsn_ = Str::EMPTY_STR;
    }

    int cLen = contentStart_.length() + content.length();
    Str cLenStr = Str( cLen, 10 );
    const char crlfcrlf[] = "\r\n\r\n";

    outgoingSize_ = headerStart_.length() + cLenStr.length() + 4 + contentStart_.length()
                    + content.length() + 4;
    outgoing_ = new char[outgoingSize_ + 1];

    size_t offset = 0;
    memcpy( outgoing_ + offset, headerStart_.cStr(), headerStart_.length() );
    offset += headerStart_.length();
    memcpy( outgoing_ + offset, cLenStr.cStr(), cLenStr.length() );
    offset += cLenStr.length();
    memcpy( outgoing_ + offset, crlfcrlf, 4 );
    offset += 4;
    memcpy( outgoing_ + offset, contentStart_.cStr(), contentStart_.length() );
    offset += contentStart_.length();
    memcpy( outgoing_ + offset, content.cStr(), content.length() );
    offset += content.length();
    memcpy( outgoing_ + offset, crlfcrlf, 4 );
    offset += 4;
    outgoing_[offset] = 0;

    //DEBUG_LOG( "About to write " + Str( outgoingSize_, 10 ) + " bytes: " + Str( outgoing_, outgoingSize_ ) )

    return dataWriting();
}

DataOverHttps::DataState DataOverHttps::dataWriting()
{
    if( verbosity_ > 0 ) logger_.syslog( "dataWriting " + Str( outgoingSize_, 10 ) + " bytes: " + Str( outgoing_ ), verbosity_ > 2 ? Syslog::IMPORTANT : Syslog::INFO );

    if( dashSSL_ )
    {
        int ssl_stat = SSL_write( sslHandle_, outgoing_, outgoingSize_ );
        if( ssl_stat > 0 )
        {
            if( verbosity_ > 0 ) logger_.syslog( "dataWriting with SSL WROTE " + Str( ssl_stat, 10 ) + " bytes: " + Str( outgoing_ ), verbosity_ > 2 ? Syslog::IMPORTANT : Syslog::INFO );
        }
        else
        {
            int ssl_err = SSL_get_error( sslHandle_, ssl_stat );
            if( verbosity_ > 0 ) logger_.syslog( "dataWriting SSL error: ", ssl_err, verbosity_ > 2 ? Syslog::IMPORTANT : Syslog::INFO );
            if( ssl_err != SSL_ERROR_WANT_READ && ssl_err != SSL_ERROR_WANT_WRITE )
            {
                int err;
                while( 0 != ( err = ERR_get_error() ) )
                {
                    ERR_error_string( err, sslErrBuf_ );
                    logger_.syslog( "SSL write error: ", sslErrBuf_ );
                }
                return disconnect();
            }
            return DATA_WRITING;
        }
    }
    else
    {
        int sent = send( socket_, outgoing_, outgoingSize_, 0 );
        if( verbosity_ > 0 ) logger_.syslog( "dataWriting WROTE " + Str( sent, 10 ) + " bytes: " + Str( outgoing_ ), verbosity_ > 2 ? Syslog::IMPORTANT : Syslog::INFO );
    }


    // Check that we read all the bytes
    if( decodedSize_ > 0 )
    {
        if( !DataReceiver::ReceiveSbd( ( const char* ) decoded_,
                                       decodedSize_, keyText_.asString().cStr(),
                                       logger_ ) )
        {
            Str hex;
            StrIOStream hexStream( hex );
            hexStream.writeHex( ( char* )decoded_, decodedSize_ );
            logger_.syslog( Str( "Failed to parse uplink message:" ) + hex,
                            Syslog::CRITICAL );
        }
        decodedSize_ = 0;
        cmdReceived_ = true;

    }

    // Prepare for DataRead
    bufferPos_ = 0;
    dataStart_ = 0;
    dataSize_ = -1;
    delete[] outgoing_;
    outgoing_ = NULL;
    outgoingSize_ = 0;

    return DATA_READ;
}

DataOverHttps::DataState DataOverHttps::dataRead()
{
    DEBUG_LOG( "dataRead" )
    int received;

    if( dashSSL_ )
    {
        received = SSL_read( sslHandle_, buffer_ + bufferPos_,
                             BUFFER_SIZE - 1 - bufferPos_ );
    }
    else
    {
        received = recv( socket_, buffer_ + bufferPos_,
                         BUFFER_SIZE - 1 - bufferPos_, MSG_DONTWAIT );
    }

    if( verbosity_ > 0 ) logger_.syslog( "dataread() @" + Str( __LINE__ ) + " received: " + Str( received ), verbosity_ > 2 ? Syslog::INFO : Syslog::DEBUG );

    if( received == -1 )
    {
        if( dashSSL_ )
        {
            int ssl_err = SSL_get_error( sslHandle_, received );
            if( verbosity_ > 0 ) logger_.syslog( "SSL_get_error: ", ssl_err, verbosity_ > 2 ? Syslog::IMPORTANT : Syslog::INFO );
            if( ssl_err != SSL_ERROR_WANT_READ && ssl_err != SSL_ERROR_WANT_WRITE )
            {
                int err;
                while( 0 != ( err = ERR_get_error() ) )
                {
                    ERR_error_string( err, sslErrBuf_ );
                    logger_.syslog( "SSL write/read error: ", sslErrBuf_ );
                }
                if( verbosity_ > 0 ) logger_.syslog( "return DISCONNECT @" + Str( __LINE__ ), verbosity_ > 2 ? Syslog::IMPORTANT : Syslog::INFO );
                return DISCONNECT;
            }
        }
        else
        {
            if( errno != EAGAIN || errno != EWOULDBLOCK )
            {
                if( verbosity_ > 0 ) logger_.syslog( "disconnect @" + Str( __LINE__ ), verbosity_ > 2 ? Syslog::IMPORTANT : Syslog::INFO );
                return disconnect();
            }
        }
    }
    else if( received == 0 )
    {
        int ssl_err = SSL_get_error( sslHandle_, received );
        if( verbosity_ > 0 ) logger_.syslog( "disconnect @" + Str( __LINE__ ) + " due to: " + Str( ssl_err ), verbosity_ > 2 ? Syslog::IMPORTANT : Syslog::INFO );
        return disconnect();
    }
    else
    {
        bufferPos_ += received;
        buffer_[bufferPos_] = '\0';
        if( dataSize_ == -1 )
        {
            char* clAt = strstr( buffer_, "Content-Length:" );
            if( NULL != clAt )
            {
                char* crlfAt = strstr( clAt, "\r\n" );
                if( NULL != crlfAt )
                {
                    dataSize_ = atoi( clAt + 15 );
                }
            }
            else
            {

            }
        }
        if( dataSize_ >= 0 && dataStart_ <= 0 )
        {
            char* crlfcrlfAt = strstr( buffer_, "\r\n\r\n" );
            if( NULL != crlfcrlfAt )
            {
                dataStart_ = crlfcrlfAt - buffer_ + 4;
            }
        }
        if( dataSize_ >= 0 && dataStart_ > 0 && dataStart_ < bufferPos_ )
        {
            if( bufferPos_ < dataSize_ + dataStart_ )
            {
                int oldDataSize = dataSize_;
                dataSize_ = bufferPos_ - dataStart_;
                logger_.syslog( "Buffer overrun, trimmed dataSize_ from "
                                + Str( oldDataSize ) + " bytes to "
                                + Str( dataSize_ ) + " bytes.", Syslog::FAULT );
            }
            lastComms_ = Timestamp::Now();
            KeyType key = KEY_UNKNOWN;
            char* value( NULL );
            char* momsn( NULL );
            char* mtmsn( NULL );
            int fileSize( 0 );
            char* data = buffer_ + dataStart_;
            *filename_ = 0;
            decodedSize_ = 0;
            if( debug_ ) logger_.syslog( "dataRead() @" + Str( __LINE__ ) + " received: " + Str( data ), verbosity_ > 2 ? Syslog::INFO : Syslog::DEBUG );
            while( ParseDataRead( &data, key, &value ) )
            {
                if( debug_ ) logger_.syslog( "ParseDataRead( data = " + Str( data ) + ", key = " + Str( key ) + ", value = " + Str( value ), verbosity_ > 2 ? Syslog::INFO : Syslog::DEBUG );
                switch( key )
                {
                case KEY_BUSY:
                    busy_ = strncmp( value, "false", 6 ) != 0;
                    break;
                case KEY_FILE:
                    decodedSize_ = Encoder::UrlDecode( value, decoded_, BUFFER_SIZE );
                    break;
                case KEY_FILENAME:
                    Encoder::UrlDecode( value, filename_, BUFFER_SIZE );
                    break;
                case KEY_FILESIZE:
                    fileSize = atoi( value );
                    break;
                case KEY_MOMSN:
                    momsn = value;
                    break;
                case KEY_MTMSN:
                    mtmsn = value;
                    break;
                case KEY_VEHICLE:
                    break;
                default:
                    break;
                }
            }
            if( 0 != *filename_ )
            {
                if( sendFilename_ == filename_ )
                {
                    Str sendFilenameBak = sendFilename_ + ".bak";
                    rename( sendFilename_.cStr(), sendFilenameBak.cStr() );
                    logger_.syslog( "Moved sent file to " + sendFilenameBak, Syslog::INFO );
                    sendFilename_ = Str::EMPTY_STR;
                    this->resetFailCount();
                }
                else
                {
                    logger_.syslog( "Server acknowledged file " + Str( filename_ ) + ", not" + sendFilename_, Syslog::FAULT );
                }
            }
            if( NULL != mtmsn )
            {
                lastMtmsn_ = mtmsn;

                // Check that we read all the bytes
                if( decodedSize_ > 0 && decodedSize_ != ( size_t ) fileSize )
                {
                    logger_.syslog(
                        "Error decoding received SBD, decoded size="
                        + Str( decodedSize_, 10 )
                        + ", indicated size=" + Str( fileSize, 10 ),
                        Syslog::FAULT );
                    decodedSize_ = 0;
                }


            }
            if( NULL != momsn && NULL != mtmsn )
            {
                logger_.syslog(
                    "SBD MOMSN=" + Str( momsn ) + ", MTMSN=" + Str( mtmsn ),
                    Syslog::IMPORTANT );
            }
            else if( NULL != mtmsn )
            {
                logger_.syslog( "SBD MTMSN=" + Str( mtmsn ), Syslog::IMPORTANT );
            }
            else if( NULL != momsn )
            {
                logger_.syslog( "SBD MOMSN=" + Str( momsn ), Syslog::INFO );
            }
            return disconnect();
        }
    }

    if( connectStart_.elapsed().asFloat() > connectionTimeout_ )
    {
        logger_.syslog( "Exceeded connection timeout, disconnecting.",
                        Syslog::INFO );
        return disconnect();
    }
    return DATA_READ;
}

DataOverHttps::DataState DataOverHttps::disconnect()
{
    if( simulateHardware() )
    {
        return DISCONNECTED;
    }
    DEBUG_LOG( "disconnect" )
    if( NULL != sslHandle_ )
    {
        SSL_shutdown( sslHandle_ );
        SSL_free( sslHandle_ );
        sslHandle_ = NULL;
    }
    if( NULL != sslContext_ )
    {
        SSL_CTX_free( sslContext_ );
        sslContext_ = NULL;
    }
    if( -1 < socket_ )
    {
        close( socket_ );
        socket_ = -1;
    }
    if( NULL != outgoing_ )
    {
        delete[] outgoing_;
        outgoing_ = NULL;
        outgoingSize_ = 0;
    }
    return DISCONNECTED;
}

