// snfNETmgr.cpp
//
// (C) Copyright 2006 - 2009 ARM Research Labs, LLC
// See www.armresearch.com for the copyright terms.
//
// See snfNETmgr.hpp for details.

#include <sys/types.h>
#include <sys/stat.h>
#include <ctime>
#include <cstring>
#include <string>
#include <vector>
#include <fstream>
#include <sstream>
#include "snfNETmgr.hpp"
#include "snf_sync.hpp"
#include "mangler.hpp"
#include "base64codec.hpp"
// #include "tcp_watchdog.hpp" No longer using TCPWatchdog -- see below _M

using namespace std;

//// snfNETmgr /////////////////////////////////////////////////////////////////

const ThreadType snfNETmgr::Type("snfNETManager");                              // The thread's type.

const ThreadState snfNETmgr::Sleeping("Sleeping");                              // Taking a break.
const ThreadState snfNETmgr::SYNC_Connect("Connecting");                        // Connecting to SYNC server.
const ThreadState snfNETmgr::SYNC_Read_Challenge("Reading challenge");          // Reading challenge.
const ThreadState snfNETmgr::SYNC_Compute_Response("Computing crypto");         // Computing crypto response.
const ThreadState snfNETmgr::SYNC_Send_Response("Sending crypto");              // Sending crypto response.
const ThreadState snfNETmgr::SYNC_Read_Availabilty("Reading Availability");     // Reading rulebase status.
const ThreadState snfNETmgr::SYNC_Send_GBUdb_Alerts("Sending GBUdb");           // Sending GBUdb alerts.
const ThreadState snfNETmgr::SYNC_Send_Status_Reports("Sending Status");        // Sending status reports.
const ThreadState snfNETmgr::SYNC_Send_Samples("Sending Samples");              // Sending message samples.
const ThreadState snfNETmgr::SYNC_Send_End_Of_Report("Sending End");            // Sending end of client data.
const ThreadState snfNETmgr::SYNC_Read_Server_Response("Reading Server");       // Reading server data.
const ThreadState snfNETmgr::SYNC_Close_Connection("Closing Connection");       // Closing connection.
const ThreadState snfNETmgr::SYNC_Parse_GBUdb_Reflections("Parsing GBUdb");     // Parsing GBUdb reflections.
const ThreadState snfNETmgr::SYNC_Log_Event("Logging SYNC");                    // Logging SYNC event.

snfNETmgr::snfNETmgr() :                                                        // Starting up the NETmgr
  Thread(snfNETmgr::Type, "NET Manager"),                                       // Network manager and Name.
  SYNCTimer(30000),                                                             // Sync every 30 secs by default.
  SyncSecsOverride(-1),                                                         // Override is -1 by default.
  myLOGmgr(NULL),
  isTimeToStop(false),
  isConfigured(false) {                                                         // On construction, NETmgr
    run();                                                                      // runs it's thread.
}

snfNETmgr::~snfNETmgr() {                                                       // On descruction, NETmgr must
    stop();                                                                     // Stop it's thread (if not already)
    myLOGmgr = NULL;                                                            // Clear out the LOGmgr hookup
    isConfigured = false;                                                       // and the configured flag.
}

void snfNETmgr::stop() {                                                        // The stop method...
    if(!isTimeToStop) {                                                         // only does it's work once:
        isTimeToStop = true;                                                    // tells it's thread to stop
        join();                                                                 // and waits for it to shut down.
    }
}

void snfNETmgr::myTask() {                                                      // Here's the thread task.
    Sleeper WaitASecond(1000);                                                  // Heartbeat timer.
    while(false == isTimeToStop) {                                              // Until it's time to stop,
        CurrentThreadState(Sleeping);                                           // post our status,
        WaitASecond();                                                          // pause for a second,
        if(isConfigured) {                                                      // then poll our tasks.

            // Do stuff here that requires configuration data.

            if(SYNCTimer.isExpired()) { sync(); SYNCTimer.restart(); }          // If it's time to sync - do it :-)

        }
    }
}

void snfNETmgr::linkLOGmgr(snfLOGmgr& L) {                                      // Set the LOGmgr.
    myLOGmgr = &L;
}

void snfNETmgr::linkGBUdbmgr(snfGBUdbmgr& G) {                                  // Set the GBUdbmgr.
    myGBUdbmgr = &G;
}

// In theory, configure will get called each time the rulebase manager loads
// a new configuration / rulebase. The configure() method updates the bits of
// NETmgr that run background tasks. Live-Data tasks pass their grab()bed
// CFGData object in order to maintain self-consistency.

void snfNETmgr::configure(snfCFGData& CFGData) {                                // Update the configuration.
    ScopeMutex CFGDataExchange(ConfigMutex);                                    // Lock the config data during updates.

    // Update the internal config data from CFGData while we are locked.
    // Internal functions which depend on this data will lock the object,
    // grab the bits they depend upon for that pass, and then unlock.

    RulebaseFilePath = CFGData.RuleFilePath;                                    // Where we can find our rulebase?
    SyncHostName = CFGData.network_sync_host;                                   // Where do we connect to sync?
    SyncHostPort = CFGData.network_sync_port;                                   // What port do we use to sync?

    HandshakeFilePath = CFGData.paths_workspace_path + ".handshake";            // Where we store our handshake.
    UpdateReadyFilePath = CFGData.paths_workspace_path + "UpdateReady.txt";     // Where we put update trigger files.

    const int SecsAsms = 1000;                                                  // Multiplier - seconds to milliseconds.

    SyncSecsConfigured = CFGData.network_sync_secs;                             // Capture the configured sync time.

    if(0 > SyncSecsOverride) {                                                  // If the sync timer isn't in override,
        if(SYNCTimer.getDuration() != (SyncSecsConfigured * SecsAsms)) {        // And the config time is different than
            SYNCTimer.setDuration(SyncSecsConfigured * SecsAsms);               // the timer's current setting then set
        }                                                                       // the timer to the new value.
    }                                                                           // If we are in override, timer is set.

    License = CFGData.node_licenseid;                                           // Capture our node id (license id).
    SecurityKey = CFGData.SecurityKey;                                          // Capture our security key.
    evolvePad(CFGData.SecurityKey);                                             // Seed our Pad generator with it.

    // Safety check before turning this on ;-)

    if(
      NULL != myLOGmgr &&
      NULL != myGBUdbmgr
      ) {                                                                       // If we are properly linked then
        isConfigured = true;                                                    // at this point we are configured!
    }
}

void snfNETmgr::sendSample(                                                     // Send a sampled message...
  snfCFGData& CFGData,                                                          // Use this configuration,
  snfScanData& ScanData,                                                        // Include this scan data,
  const unsigned char* MessageBuffer,                                           // This is the message itself
  int MessageLength                                                             // and it is this size.
  ) {
    string TimeStamp; (*myLOGmgr).Timestamp(TimeStamp);                         // Grab a timestamp.
    ostringstream XML;                                                          // Make formatting easier with this.

    //-- <sample...>

    XML << "<sample node=\'" << CFGData.node_licenseid << "\' "
        << "time=\'" << TimeStamp << "\' "
        << "result=\'" << ScanData.CompositeFinalResult << "\'>" << endl;

    //-- <ip...>

    XML << "<ip range=\'";
    string IPRange;
    switch(ScanData.SourceIPRange()) {
        case Unknown:   { IPRange = "Unknown"; break; }                         // Unknown - not defined.
        case White:     { IPRange = "White"; break; }                           // This is a good guy.
        case Normal:    { IPRange = "Normal"; break; }                          // Benefit of the doubt.
        case New:       { IPRange = "New"; break; }                             // It is new to us.
        case Caution:   { IPRange = "Caution"; break; }                         // This is suspicious.
        case Black:     { IPRange = "Black"; break; }                           // This is bad.
        case Truncate:  { IPRange = "Truncate"; break; }                        // Don't even bother looking.
    }

    SocketAddress IP;
    IP.setAddress(ScanData.SourceIPRecord().IP);

    XML << IPRange << "\' ip=\'" << (string) IP4Address(IP.getAddress()) << "\' t=\'";

    string IPType;
    switch(ScanData.SourceIPRecord().GBUdbData.Flag()) {
        case Good:      { IPType = "Good"; break; }
        case Bad:       { IPType = "Bad"; break; }
        case Ugly:      { IPType = "Ugly"; break; }
        case Ignore:    { IPType = "Ignore"; break; }
    }

    XML << IPType << "\' b=\'" << ScanData.SourceIPRecord().GBUdbData.Bad()
        << "\' g=\'" << ScanData.SourceIPRecord().GBUdbData.Good()
        << "\'/>" << endl;

    //-- <match...> as many as needed

    if(0 < ScanData.MatchRecords.size()) {                                      // If we have match records - emit them.
        list<snf_match>::iterator iM;                                           // Grab an iterator.
        for(                                                                    // Emit each snf_match entry.
          iM = ScanData.MatchRecords.begin();
          iM != ScanData.MatchRecords.end();
          iM++) {
            XML << "<match r=\'" << (*iM).ruleid << "\' "
                << "g=\'" << (*iM).symbol << "\' "
                << "i=\'" << (*iM).index << "\' "
                << "e=\'" << (*iM).endex << "\' "
                << "f=\'" << (*iM).flag << "\'/>";
        }
    }

    //-- <msg...>

    XML << "<msg size=\'" << ScanData.ScanSize << "'>" << endl;                 // Starting with the msg element.
    to_base64 EncodedMessageData(
      reinterpret_cast<const char*>(MessageBuffer), MessageLength);             // Encode the message to base64.

    const int SampleLineLength = 64;                                            // 64 bytes per line is good.
    for(int i = 0; i < MessageLength;) {                                        // Now we break it into lines
        for(int l = 0; l < SampleLineLength && i < MessageLength; l++, i++) {   // that are a reasonable length.
            XML << EncodedMessageData.at(i);                                    // Emit one character at a time...
        }                                                                       // At the end of a reasonable
        XML << endl;                                                            // length we terminate the line.
    }
    XML << "</msg>" << endl;                                                    // End of the <msg> element.

    //-- done with the sample!

    XML << "</sample>" << endl;

    // Last thing we do is post the formatted string to the buffer.
    const int SampleSafetyLimit = 100000;                                       // 100 Kbyte limit on samples.
    ScopeMutex DoNotDisturb(myMutex);                                           // Don't bug me man I'm busy.
    if(SampleSafetyLimit < SamplesBuffer.length())                              // If the samples buffer is full
      SamplesBuffer.clear();                                                    // clear it before adding more.
    SamplesBuffer.append(XML.str());                                            // Append the XML to the buffer.
}

string snfNETmgr::getSamples() {                                                // Synchronized way to get Samples.
    ScopeMutex DoNotDisturb(myMutex);                                           // Lock the mutex to protect our work.
    string SamplesBatch = SamplesBuffer;                                        // Copy the samples to a new string.
    SamplesBuffer.clear();                                                      // Clear the samples buffer.
    return SamplesBatch;                                                        // Return a batch of Samples.
}

void snfNETmgr::sendReport(const string& S) {                                   // How to send a status report.
    const int ReportSafetyLimit = 100000;                                       // 100 Kbytes limit on reports.
    ScopeMutex DoNotDisturb(myMutex);                                           // Lock the mutex for a moment.
    if(ReportSafetyLimit < ReportsBuffer.length())                              // If the reports buffer is full
      ReportsBuffer.clear();                                                    // clear it before adding more.
    ReportsBuffer.append(S);                                                    // Append the report.
}

string snfNETmgr::getReports() {                                                // Synchronized way to get Reports.
    ScopeMutex DoNotDisturb(myMutex);                                           // Lock the mutex to protect our work.
    string ReportsBatch = ReportsBuffer;                                        // Copy the reports to a new string.
    ReportsBuffer.clear();                                                      // Clear the reports buffer.
    return ReportsBatch;                                                        // Return a batch of Reports.
}

string& snfNETmgr::RulebaseUTC(string& t) {                                     // Gets local rulebase file UTC.
    struct stat RulebaseStat;                                                   // First we need a stat buffer.
    if(0 != stat(RulebaseFilePath.c_str(), &RulebaseStat)) {                    // If we can't get the stat we
        t.append("000000000000"); return t;                                     // will return 000000000000 to
    }                                                                           // make sure we should get the file.
    struct tm RulebaseTime;                                                     // Allocate a time structure.
    RulebaseTime = *(gmtime(&RulebaseStat.st_mtime));                           // Copy the file time to it as UTC.

    char TimestampBfr[20];                                                       // Timestamp buffer.

    sprintf(TimestampBfr,"%04d%02d%02d%02d%02d%02d\0",                           // Format yyyymmddhhmmss
      RulebaseTime.tm_year+1900,
      RulebaseTime.tm_mon+1,
      RulebaseTime.tm_mday,
      RulebaseTime.tm_hour,
      RulebaseTime.tm_min,
      RulebaseTime.tm_sec
    );

    t.append(TimestampBfr);                                                     // Append the timestamp to t
    return t;                                                                   // and return it to the caller.
}

unsigned long snfNETmgr::ResolveHostIPFromName(const string& N) {               // Host name resolution tool.
    ScopeMutex OneAtATimePlease(ResolverMutex);                                 // Resolve only one at a time.
    unsigned long IP = inet_addr(N.c_str());                                    // See if it's an IP.
    if (INADDR_NONE == IP) {                                                    // If it's not an IP resolve it.
        hostent* H = gethostbyname(N.c_str());                                  // Resolve the host.
        if (NULL == H) {                                                        // If we didn't get a resolution
            return INADDR_NONE;                                                 // return no address.
        }                                                                       // If we did resolve the address
        IP = *((unsigned long*)H->h_addr_list[0]);                              // get the primary entry.
    }
    return ntohl(IP);                                                           // Return what we got (host order)
}

// The Evolving One Time Pad engine is just slightly better than calling
// rand() with the system time as a seed. However, it does have the advantage
// that in order to guess it's initial state an attacker would need to already
// know the license id and authentication. It also has the advantage that it
// adds small amounts of entropy over time and never really forgets them. For
// example, the exact time between calls to evolvePad is dependent on how long
// it takes to sync which is dependent on how much data there is to report
// which is dependent on the number and size of messages scanned etc... and
// this is also impacted a bit by network performance issues during the sync.
// Sensitivity to this entropy has millisecond resolution. This is a cross-
// platform solution that depends only on our own code ;-)

void snfNETmgr::evolvePad(string Entropy) {                                     // Add entropy and evolve.
    ScopeMutex OneAtATimePlease(PadMutex);                                      // Protect the one time pad.
    myLOGmgr->Timestamp(Entropy);                                               // Time matters ;-)
    int x;                                                                      // We want to capture this.
    for(int a = 0; a < Entropy.length(); a++) {                                 // Add the entropy to our generator.
        x = PadGenerator.Encrypt(Entropy.at(a));
    }
    msclock rt = myLOGmgr->RunningTime();                                       // Get the elapsed running time so far.
    unsigned char* rtb = reinterpret_cast<unsigned char*>(&rt);                 // Convert that long long into bytes.
    for(int a = 0; a < sizeof(msclock); a++) {                                  // Encrypt those bytes one by one
        PadGenerator.Encrypt(rtb[a]);                                           // to add more entropy.
    }
}

// To get a pad of any length you like, use the OneTimePad()
// Note that we don't assign a value to x before using it! If we get lucky,
// we will get some random value from ram as additional entropy ;-) If we end
// up starting with zero, that's ok too.

PadBuffer snfNETmgr::OneTimePad(int Len) {                                      // Get Len bytes of one time pad.
    PadBuffer B;                                                                // Start with a buffer.
    B.reserve(Len);                                                             // Reserve Len bytes.
    unsigned char x;                                                            // Get an unsigned char, unknown value.
    for(int a = 0; a < Len; a++) {                                              // Create Len bytes of pad by evolving
        B.push_back(x = PadGenerator.Encrypt(x));                               // x through itself and copying the
    }                                                                           // data into the buffer.
    return B;                                                                   // Return the result.
}

// Handshake tries to return the current stored handshake. If it can't then it
// returns a new handshake based on data from the pad generator.

PadBuffer snfNETmgr::Handshake() {                                              // What is the current handshake?
    if(CurrentHandshake.size() != SNFHandshakeSize) {                           // If we don't have one make one!
        CurrentHandshake = OneTimePad(SNFHandshakeSize);                        // Set up a default handshake to use
        try {                                                                   // if we can't remember the real one.
            ifstream HSF(HandshakeFilePath.c_str(), ios::binary);               // Open the handshake file.
            char* bfr = reinterpret_cast<char*>(&CurrentHandshake[0]);          // Manufacture a proper pointer.
            HSF.read(bfr, SNFHandshakeSize);                                    // Read the data (overwrite the HSB).
            HSF.close();                                                        // Close the file.
        } catch(...) { }                                                        // Ignore any errors.
    }
    return CurrentHandshake;                                                    // Return the buffer.
}

PadBuffer& snfNETmgr::Handshake(PadBuffer& NewHandshake) {                      // Store a new handshake.
    CurrentHandshake = NewHandshake;                                            // Grab the new handshake
    try {                                                                       // then try to store it...
        ofstream HSF(HandshakeFilePath.c_str(), ios::binary | ios::trunc);      // Open the handshake file.
        char* bfr = reinterpret_cast<char*>(&NewHandshake[0]);                  // Access the raw buffer.
        HSF.write(bfr, NewHandshake.size());                                    // Replace the old handshake
        HSF.close();                                                            // close the file.
    } catch(...) {}                                                             // Ignore errors.
    return NewHandshake;                                                        // Return what we were given.
}

void snfNETmgr::postUpdateTrigger(string& updateUTC) {                          // Post an update trigger file.
    try {                                                                       // Safely post an update trigger.
        ofstream HSF(UpdateReadyFilePath.c_str(), ios::binary | ios::trunc);    // Open/create the trigger file.
        char* bfr = reinterpret_cast<char*>(&updateUTC[0]);                     // Access the raw UTC buffer.
        HSF.write(bfr, updateUTC.size());                                       // Write the update timestamp.
        HSF.close();                                                            // close the file.
    } catch(...) {}                                                             // Ignore errors.
}

// Utility to read a line from a non-blocking TCPHost & check the timeout.

const int MaxReadLineLength = 1024;                                             // How long a line can be.
string readLineTimeout(TCPHost& S, Timeout& T) {                                // Read a line from S until T.
    Sleeper WaitForMoreData(50);                                                // How long to wait when no data.
    string LineBuffer = "";                                                     // Buffer for the line.
    while(                                                                      // Keep going as long as:
      false == T.isExpired() &&                                                 // our timeout has not expired AND
      MaxReadLineLength > LineBuffer.length()                                   // we haven't reached our limit.
      ) {
        char c = 0;                                                             // One byte at a time
        if(1 == S.receive(&c, sizeof(c))) {                                     // Read from the TCPHost.
            LineBuffer.push_back(c);                                            // Push the byte onto the string.
            if('\n' == c) break;                                                // If it was a newline we're done!
        } else {                                                                // If we didn't get any data
            WaitForMoreData();                                                  // pause before our next run.
        }
    }
    return LineBuffer;                                                          // Always return our buffer.
}

// Utility to write data to a non-blocking TCPHost & check the timeout.

// Some networks can only handle small packets and fragmentation can be a
// problem. Also, on Win* especially, sending small chunks is _MUCH_ more
// reliable than trying to send large buffers all at once. SO - here we break
// down our sending operations into medium sized chunks of data. The underlying
// os can reorganize these chunks as needed for the outgouing stream. If the OS
// needs us to slow down (doesn't send full chunks) then we introduce a small
// delay between chunks to give the channel more time.

const int MaxSendChunkSize = 512;                                               // Size of one chunk in a write.
void sendDataTimeout(TCPHost& S, Timeout& T, char* Bfr, int Len) {              // Send and keep track of time.
    Sleeper WaitForMoreRoom(15);                                                // Wait to send more data.
    int Remaining = Len;                                                        // This is how much we have left.
    while(                                                                      // For as long as:
      false == T.isExpired() &&                                                 // We still have time left AND
      0 < Remaining                                                             // We still have data left
      ) {
        int ThisChunkSize = Remaining;                                          // Hope to send it all in one chunk
        if(MaxSendChunkSize < ThisChunkSize) ThisChunkSize = MaxSendChunkSize;  // but break it down as needed.
        int SentThisTime = S.transmit(Bfr, ThisChunkSize);                      // Send the data. How much went?
        Remaining -= SentThisTime;                                              // Calculate how much is left.
        Bfr += SentThisTime;                                                    // Move our pointer (old school!)
        if(ThisChunkSize > SentThisTime) WaitForMoreRoom();                     // If some of this chunk didn't go
    }                                                                           // the pause before the next chunk.
}

void sendDataTimeout(TCPHost& S, Timeout& T, string& D) {                       // Send a string and keep track
    sendDataTimeout(S, T, const_cast<char*>(D.c_str()), D.length());            // of time. (Polymorphism is fun)
}

void snfNETmgr::sync() {                                                        // Synchronize with central command.

    // Keep these things in scope. This is how we roll.

    string HostName;
    int HostPort;
    string Secret;
    string Node;

    // Grab our configuration data (marchng orders).

    if(!isConfigured) return;                                                   // If we're not configured, don't!
    else {
        ScopeMutex GettingConfig(ConfigMutex);                                  // Temporarily lock our config.
        HostName = SyncHostName;                                                // We will connect to this host.
        HostPort = SyncHostPort;                                                // We will connect to this port.
        Secret = SecurityKey;                                                   // Get the security key.
        Node = License;                                                         // Get the Node ID.
    }

    try {                                                                       // Lots can go wrong so catch it :-)

        // 20080326 _M Blocking sockets tend to lock up so I've refactored this
        // code to use non-blocking sockets. This is actually part of the previous
        // refactor (TCPWatchdog see below) since without the watchdog there is no
        // way to get out of a blocking socket if it's dead.

        // 20080325 _M TCPWatchdog is a brute. It doesn't pay attention to thread
        // states. A weird bug showed up where the SYNC session seemed to hang and
        // the TCPWatchdog was left alive. In the process of hunting down this bug
        // I decided to remove the TCPWatchdog and put appropriate timeout checking
        // in each of the comms loops instead. So, from now on:
        // if(SessionDog.isExpired()) throw SyncFailed("Out Of Time");

        const int SyncSessionTimeout = 2 * SYNCTimer.getDuration();             // Timeout is twice poll time.
        Timeout SessionDog(SyncSessionTimeout);                                 // Give this long for a session.

        // Connect to the sync host.

        CurrentThreadState(SYNC_Connect);

        SocketAddress SyncHostAddress;                                          // We'll need an address.
        SyncHostAddress.setPort(HostPort);                                      // Set the port.
        SyncHostAddress.setAddress(ResolveHostIPFromName(HostName));            // Resolve and set the IP.
        TCPHost SyncServer(SyncHostAddress);                                    // Set up a host connection.
        SyncServer.makeNonBlocking();                                           // Make the connection non-blocking.

        PollTimer WaitForOpen(10, 340);                                         // Expand 10ms to 340ms between tries.
        while(!SessionDog.isExpired()) {                                        // Wait & Watch for a good connection.
            try { SyncServer.open(); }                                          // Try opening the connection.
            catch(exception& e) {                                               // If we get an exception then
                string ConnectFailMessage = "snfNETmgr::sync().open() ";        // format a useful message about
                ConnectFailMessage.append(e.what());                            // the error and then throw
                throw SyncFailed(ConnectFailMessage);                           // a SyncFailed exception.
            }
            if(SyncServer.isOpen()) break;                                      // When successful, let's Go!
            else WaitForOpen.pause();                                           // When not yet successful, pause
        }                                                                       // then try again if we have time.

        if(!SyncServer.isOpen()) throw SyncFailed("Connect Timed Out");         // Check our connection.

        if(SessionDog.isExpired()) throw SyncFailed("Out Of Time");             // Check our session time.

        // Start communicating.

        string LineBuffer = "";                                                 // Input Line Buffer.

        // Read challenge

        CurrentThreadState(SYNC_Read_Challenge);

        LineBuffer = readLineTimeout(SyncServer, SessionDog);                   // Read the challenge line.
        snf_sync Challenge(LineBuffer.c_str(), LineBuffer.length());            // Interpret what we read.
        if(                                                                     // Check that it's good...
          Challenge.bad() ||                                                    // A complete packet was read
          0 >= Challenge.snf_sync_challenge_txt.length()                        // and the challenge is present.
          ) throw SyncFailed("sync() Challenge.bad()");                         // If not then throw.

        if(SessionDog.isExpired()) throw SyncFailed("Out Of Time");             // Check our session time.

        // Write response

        CurrentThreadState(SYNC_Compute_Response);

        from_base64 DecodedChallenge(Challenge.snf_sync_challenge_txt);         // Decode the challenge.

        //--- Prepare the secret.

        MANGLER ResponseGenerator;                                              // Grab a mangler.
        for(int i = 0; i < Secret.length(); i++)                                // Fill it with the
          ResponseGenerator.Encrypt(Secret.at(i));                              // security key.

        const int ManglerKeyExpansionCount = 1024;                              // Loop this many to randomize.
        for(int x = 0, i = 0; i < ManglerKeyExpansionCount; i++)                // For the required number of loops,
          x = ResponseGenerator.Encrypt(x);                                     // have Mangler chase it's tail.

        //--- Absorb the challenge.

        for(int i = 0; i < DecodedChallenge.size(); i++)                        // Evolve through the challenge.
          ResponseGenerator.Encrypt(DecodedChallenge.at(i));

        /*** We now have half of the key for this session ***/

        //--- Encrypt our Pad.

        PadBuffer NewPad = OneTimePad();                                        // Grab a new Pad (default size).

        base64buffer ResponseBin;                                               // With the key now established,
        for(int i = 0; i < NewPad.size(); i++)                                  // encrypt the one time pad for
          ResponseBin.push_back(                                                // transfer.
            ResponseGenerator.Encrypt(NewPad[i]));

        //--- Encrypt our Handshake.

        PadBuffer CurrentHandshake = Handshake();                               // Recall the secret handshake.
        for(int i = 0; i < CurrentHandshake.size(); i++)                        // Encrypt that into the stream.
          ResponseBin.push_back(
            ResponseGenerator.Encrypt(CurrentHandshake[i]));

        //--- Encrypt our Signature.

        for(int x = 0, i = 0; i < SNFSignatureSize; i++)                        // Generate a hash by having Mangler
          ResponseBin.push_back(                                                // chase it's tail for the appropriate
            x = ResponseGenerator.Encrypt(x));                                  // number of bytes.

        //--- Encode our response as base64 and send it.

        to_base64 ResponseTxt(ResponseBin);                                     // Encode the cyphertext as base64.
        string ResponseTxtString;                                               // Create a handy string and place
        ResponseTxtString.assign(ResponseTxt.begin(), ResponseTxt.end());       // the base 64 text into it.

        string ResponseMsg;                                                     // Build an appropriate response
        ResponseMsg.append("<snf><sync><response nodeid=\'");                   // identifying this node
        ResponseMsg.append(Node);                                               // with the license id
        ResponseMsg.append("\' text=\'");                                       // and providing an appropriately
        ResponseMsg.append(ResponseTxtString);                                  // mangled response string
        ResponseMsg.append("\'/></sync></snf>\n");                              // for authentication.

        CurrentThreadState(SYNC_Send_Response);

        sendDataTimeout(SyncServer, SessionDog, ResponseMsg);                   // Send the response.
        if(SessionDog.isExpired()) throw SyncFailed("Out Of Time");             // Check our session time.

        // Read rulebase info or error

        CurrentThreadState(SYNC_Read_Availabilty);

        LineBuffer = readLineTimeout(SyncServer, SessionDog);                   // Read the rulebase status line.
        snf_sync RulebaseResponse(LineBuffer.c_str(), LineBuffer.length());     // Interpret what we read.
        if(                                                                     // Check that it's good...
          RulebaseResponse.bad()                                                // A complete packet was read.
          ) throw SyncFailed("sync() Response.bad()");                          // If not then throw.

        if(0 < RulebaseResponse.snf_sync_error_message.length()) {              // If the response was an error
            PadBuffer NewNullHandshake;                                         // then we will assume we are out
            NewNullHandshake.assign(SNFHandshakeSize, 0);                       // of sync with the server so we
            Handshake(NewNullHandshake);                                        // will set the NULL handshake and
            throw SyncFailed("sync() Response error message");                  // fail this sync attempt.
        }
        if(SessionDog.isExpired()) throw SyncFailed("Out Of Time");             // Check our session time.

        // Update Handshake

        for(int x = 0, i = 0; i < ManglerKeyExpansionCount; i++)                // For the required number of loops,
          x = ResponseGenerator.Encrypt(x);                                     // have Mangler chase it's tail.

        PadBuffer NewHandshake;                                                 // Grab a new handshake buffer.
        for(int x = 0, i = 0; i < SNFHandshakeSize; i++)                        // Create the new handshake as a
          NewHandshake.push_back(                                               // mangler hash of the current
            x = ResponseGenerator.Encrypt(x));                                  // key state (proper length of course).

        Handshake(NewHandshake);                                                // Save our new handshake to disk.

        // Interpret Rulebase Response

        myLOGmgr->updateAvailableUTC(RulebaseResponse.snf_sync_rulebase_utc);   // Store the latest update UTC.
        if(myLOGmgr->isUpdateAvailable()) {                                     // If a new update is read then
            postUpdateTrigger(RulebaseResponse.snf_sync_rulebase_utc);          // create an update trigger file.
        }

        // Write our Client reports (multi-line)

        CurrentThreadState(SYNC_Send_GBUdb_Alerts);

        string ClientReport;
        ClientReport.append("<snf><sync><client>\n");
        sendDataTimeout(SyncServer, SessionDog, ClientReport);
        ClientReport = "";

        if(SessionDog.isExpired()) throw SyncFailed("Out Of Time");             // Check our session time.

        // Insert our GBUdb Alerts.

        list<GBUdbAlert> Alerts;                                                // Make a list of GBUdb Alerts.
        myGBUdbmgr->GetAlertsForSync(Alerts);                                   // Get them from our GBUdb.
        list<GBUdbAlert>::iterator iA;
        for(iA = Alerts.begin(); iA != Alerts.end(); iA++) {                    // Convert each alert in our list
            ClientReport.append((*iA).toXML());                                 // into XML, follow it up
            ClientReport.append("\n");                                          // with a new line, and send it
        }
        sendDataTimeout(SyncServer, SessionDog, ClientReport);                  // Send the Client report data.
        ClientReport = "";                                                      // Clear the buffer.
        if(SessionDog.isExpired()) throw SyncFailed("Out Of Time");             // Check our session time.

        // Send Status Reports - one line at a time.

        CurrentThreadState(SYNC_Send_Status_Reports);

        /**
        *** Instead of splitting up the reports by line we will try sending them
        *** all at once using the new sendDataTimeout() function.
        ***
        if(0 < ReportsBuffer.length()) {                                        // If we have reports - send them.
            string DataToSend = getReports();                                   // Grab a copy and clear the buffer.
            int Cursor = 0;                                                     // We need a cursor and a length
            int Length = 0;                                                     // to help us feed this line by line.
            while(Cursor < DataToSend.length()) {                               // While we have more data...
                Length = DataToSend.find_first_of('\n', Cursor);                // Find the end of the first line.
                if(string::npos == Length) break;                               // If we can't then we're done.
                Length = (Length + 1) - Cursor;                                 // If we can, convert that to length.
                SyncServer.transmit(                                            // Get and send the line using the
                  DataToSend.substr(Cursor, Length).c_str(),                    // substring function.
                  Length
                );
                Cursor = Cursor + Length;                                       // Move the cursor for the next line.
                if(SessionDog.isExpired()) throw SyncFailed("Out Of Time");     // Check our session time.
            }
        }

        **/

        if(0 < ReportsBuffer.length()) {                                        // If we have reports to send
            string DataToSend = getReports();                                   // get (and clear) the reports and
            sendDataTimeout(SyncServer, SessionDog, DataToSend);                // send them (mindful of timeout).
        }
        if(SessionDog.isExpired()) throw SyncFailed("Out Of Time");             // Check our session time.


        // Send Samples - one line at a time.

        CurrentThreadState(SYNC_Send_Samples);

        /***

        if(0 < SamplesBuffer.length()) {
            string DataToSend = getSamples();
            int Cursor = 0;                                                     // We need a cursor and a length
            int Length = 0;                                                     // to help us feed this line by line.
            while(Cursor < DataToSend.length()) {                               // While we have more data...
                Length = DataToSend.find_first_of('\n', Cursor);                // Find the end of the first line.
                if(string::npos == Length) break;                               // If we can't then we're done.
                Length = (Length + 1) - Cursor;                                 // If we can, convert that to length.
                SyncServer.transmit(                                            // Get and send the line using the
                  DataToSend.substr(Cursor, Length).c_str(),                    // substring function.
                  Length
                );
                Cursor = Cursor + Length;                                       // Move the cursor for the next line.
                if(SessionDog.isExpired()) throw SyncFailed("Out Of Time");     // Check our session time.
            }
        }

        ***/

        if(0 < SamplesBuffer.length()) {                                        // If we have samples to send
            string DataToSend = getSamples();                                   // get (and clear) the samples and
            sendDataTimeout(SyncServer, SessionDog, DataToSend);                // send them (mindful of timeout).
        }
        if(SessionDog.isExpired()) throw SyncFailed("Out Of Time");             // Check our session time.

        // Terminate the client messages.

        CurrentThreadState(SYNC_Send_End_Of_Report);

        ClientReport.append("</client></sync></snf>\n");

        sendDataTimeout(SyncServer, SessionDog, ClientReport);                  // Send the Client report.
        if(SessionDog.isExpired()) throw SyncFailed("Out Of Time");             // Check our session time.

        // Read the Server response (multi-line)

        CurrentThreadState(SYNC_Read_Server_Response);

        string ServerResponse;
        string ResponseLine;
        while(string::npos == ResponseLine.find("</snf>\n")) {                  // Until we find the ending...
            ResponseLine = readLineTimeout(SyncServer, SessionDog);             // Read a line.
            if(0 >= ResponseLine.length()) {                                    // If we get an empty line
                throw SyncFailed("sync() server response empty line");          // then it's an error.
            }
            ServerResponse.append(ResponseLine);                                // Append the line.
            if(SessionDog.isExpired()) throw SyncFailed("Out Of Time");         // Check our session time.
        }

        snf_sync ServerMessages(
          ServerResponse.c_str(), ServerResponse.length());                     // Interpret what we read.
        if(                                                                     // Check that it's good...
          ServerMessages.bad()                                                  // A complete packet was read.
          ) throw SyncFailed("sync() ServerMessages.bad()");                    // If not then throw.

        // At this point we should have a good Server response.

        CurrentThreadState(SYNC_Close_Connection);

        SyncServer.close();                                                     // Close the connection.
        evolvePad(Challenge.snf_sync_challenge_txt);                            // Use this event for more entropy.

        // Import any GBUdb reflections.

        CurrentThreadState(SYNC_Parse_GBUdb_Reflections);

        if(0 < ServerMessages.ServerGBUAlertHandler.AlertList.size()) {         // If we have received reflections
            myGBUdbmgr->ProcessReflections(                                     // then process them through our
              ServerMessages.ServerGBUAlertHandler.AlertList                    // GBUdb.
            );
        }

        /*** On Sync Override set sync timer to override time. If no override
        **** then be sure to reset the timer to the current CFG value if it
        **** is not already there. Also, if sync override is not engaged then
        **** be sure the overrid flag is set to -1 indicating it is off.
        **** Configure() code assumes we are handling the override sync timer
        **** functions this way.
        ***/

        // Assign the SyncSecsOverride with the value we retrieved. It will
        // either be a seconds value, or a -1 indicating it was absent from
        // the server message.

        SyncSecsOverride = ServerMessages.snf_sync_server_resync_secs;          // What was the SyncOverride?
        const int SecsAsms = 1000;                                              // Multiplier - seconds to milliseconds.

        if(0 > SyncSecsOverride) {                                              // If the sync timer IS NOT in override,
            if(SYNCTimer.getDuration() != (SyncSecsConfigured * SecsAsms)) {    // And the config time is different than
                SYNCTimer.setDuration(SyncSecsConfigured * SecsAsms);           // the timer's current setting then set
            }                                                                   // the timer to the new value.
        } else {                                                                // If the sync timer IS in override now,
            if(SYNCTimer.getDuration() != (SyncSecsOverride * SecsAsms)) {      // and the override is different than the
                SYNCTimer.setDuration(SyncSecsOverride * SecsAsms);             // current setting then override the setting
            }                                                                   // with the new value.
        }

        // All done

        CurrentThreadState(SYNC_Log_Event);

        (*myLOGmgr).RecordSyncEvent();                                          // Finished that -- so log the event.

    }
    catch (exception& e) {                                                      // SYNC Failed and we know more.
        const int snf_UNKNOWN_ERROR = 99;                                       // Report an error (unknown code)
        string ERROR_SYNC_FAILEDmsg = CurrentThreadState().Name;                // Format a useful state message.
        ERROR_SYNC_FAILEDmsg.append(": ");
        ERROR_SYNC_FAILEDmsg.append(e.what());
        (*myLOGmgr).logThisError(                                               // Log the error (if possible)
          "SNF_NETWORK", snf_UNKNOWN_ERROR, ERROR_SYNC_FAILEDmsg
        );
    }
    catch (...) {                                                               // SYNC Failed if we're here.
        const int snf_UNKNOWN_ERROR = 99;                                       // Report an error (unknown code)
        string ERROR_SYNC_FAILEDmsg = CurrentThreadState().Name;                // Format a useful state message.
        ERROR_SYNC_FAILEDmsg.append(": Panic!");
        (*myLOGmgr).logThisError(                                               // Log the error (if possible)
          "SNF_NETWORK", snf_UNKNOWN_ERROR, ERROR_SYNC_FAILEDmsg
        );
    }
}
