
#include "mgenGlobals.h"
#include "mgen.h"
#include "mgenMsg.h"
#include "mgenVersion.h"
#include "mgenEvent.h"

#include <string.h>
#include <stdio.h>   
#include <time.h>       // for gmtimeon()
#include <errno.h>      // for errno
#include <ctype.h>      // for toupper(
#ifdef UNIX
#include <sys/types.h>  // for stat
#include <sys/stat.h>   // for stat
#include <unistd.h>     // for stat
#endif 

// For WinCE, we provide an option logging function
// to get log output to our debug window
#ifndef _WIN32_WCE
#include <errno.h>
Mgen::LogFunction Mgen::Log = fprintf;
#else
Mgen::LogFunction Mgen::Log = fprintf;

/**
 * This function is used on our WinCE build where there is no
 * real "stdout" for default MGEN output
 */
int Mgen::LogToDebug(FILE* /*filePtr*/, const char* format, ...)
{
    va_list args;
    va_start(args, format);
    char charBuffer[2048];
    charBuffer[2048] = '\0';
    int count = _vsnprintf(charBuffer, 2047, format, args);
    va_end(args);
    return count;
}  // end Mgen::LogToDebug()

#endif // if/else !_WIN32_WCE

Mgen::Mgen(ProtoTimerMgr&         timerMgr,
           ProtoSocket::Notifier& socketNotifier)
  : 
  controller(NULL), socket_notifier(socketNotifier),
  timer_mgr(timerMgr), 
  save_path(NULL), save_path_lock(false), started(false),  
  start_hour(0), start_min(0), start_sec(-1.0),
  start_gmt(false), start_time_lock(false),
  offset(-1.0), offset_lock(false), offset_pending(false),
  checksum_force(false), 
  default_flow_label(0), default_label_lock(false),
  default_tx_buffer(0), default_rx_buffer(0),
  default_broadcast(true), default_tos(0), 
  default_ttl(3), default_queue_limit(0), 
  default_broadcast_lock(false),
  default_tos_lock(false), default_ttl_lock(false), 
  default_tx_buffer_lock(false), default_rx_buffer_lock(false), 
  default_interface_lock(false), default_queue_limit_lock(false),
  sink_non_blocking(true),
  log_data(true), log_gps_data(true),
  checksum_enable(false), 
  addr_type(ProtoAddress::IPv4), 
  get_position(NULL), get_position_data(NULL),
  log_file(NULL), log_binary(false), local_time(false), log_flush(false), 
  log_file_lock(false), log_tx(false), log_open(false), log_empty(true),
  reuse(true)

{
    start_timer.SetListener(this, &Mgen::OnStartTimeout);
    start_timer.SetInterval(0.0);
    start_timer.SetRepeat(0);
    
    drec_event_timer.SetListener(this, &Mgen::OnDrecEventTimeout);
    drec_event_timer.SetInterval(0.0);
    drec_event_timer.SetRepeat(-1);

    default_interface[0] = '\0';
    sink_path[0] = '\0';
    source_path[0] = '\0';

}

Mgen::~Mgen()
{
    Stop();
    if (save_path) delete save_path;
}

/**
 * Activates transmit flows according to any "offset" time.
 * Pre-processes drec events occuring prior to "offset" time.
 * Activates any group joins deferred during "offset" processing.
 * Schedules next pending event.
 */
bool Mgen::Start()
{
#ifdef HAVE_IPV6
    //Set default IPv6 flow labels for each flow
    flow_list.SetDefaultLabel(default_flow_label);
#endif //HAVE_IPV6  
    struct timeval currentTime;
    ProtoSystemTime(currentTime);
                
    if (start_sec < 0.0)
    {
        // Start immediately
        if (log_file)
        {
            // Log START event
            if (log_binary)
            {
                char buffer[128];
                if (log_empty)
                {
                    // write binary log header line plus NULL character
                    strcpy(buffer, "mgen version=");
                    strcat(buffer, MGEN_VERSION);
                    strcat(buffer, " type=binary_log\n");
                    fwrite(buffer, sizeof(char), strlen(buffer)+1, log_file);
                }
                unsigned int index = 0;
                // set "eventType" field
                buffer[index++] = (char)START_EVENT;
                // zero "reserved" field
                buffer[index++] = 0;
                // set "recordLength" field
                UINT16 temp16 = htons(8); 
                memcpy(buffer+index, &temp16, sizeof(INT16));
                index += sizeof(INT16);
                // set "eventTime" fields
                UINT32 temp32 = htonl(currentTime.tv_sec);
                memcpy(buffer+index, &temp32, sizeof(INT32));
                index += sizeof(INT32);
                temp32 = htonl(currentTime.tv_usec);
                memcpy(buffer+index, &temp32, sizeof(INT32));
                index += sizeof(INT32);
                if (fwrite(buffer, sizeof(char), index, log_file) < index)
                {
                  DMSG(0, "Mgen::Start() fwrite() error: %s\n", GetErrorString());   
                }
            }
            else
            {

#ifdef _WIN32_WCE
                struct tm timeStruct;
                timeStruct.tm_hour = currentTime.tv_sec / 3600;
                UINT32 hourSecs = 3600 * timeStruct.tm_hour;
                timeStruct.tm_min = (currentTime.tv_sec - (hourSecs)) / 60;
                timeStruct.tm_sec = currentTime.tv_sec - (hourSecs) - (60*timeStruct.tm_min);
                timeStruct.tm_hour = timeStruct.tm_hour % 24;
                struct tm* timePtr = &timeStruct;
#else           
                struct tm* timePtr;
                if (local_time)
                  timePtr = localtime((time_t*)&currentTime.tv_sec);
                else
                  timePtr = gmtime((time_t*)&currentTime.tv_sec);
                
#endif // if/else _WIN32_WCE
                Mgen::Log(log_file, "%02d:%02d:%02d.%06lu START Mgen Version %s\n",
                     timePtr->tm_hour, timePtr->tm_min, 
                          timePtr->tm_sec, (UINT32)currentTime.tv_usec,MGEN_VERSION);
            }
            if (log_empty) log_empty = false;
            fflush(log_file);
        }
        
        // Activate transmit flows according to "offset" time  
        flow_list.Start(offset);
        
        // Pre-process drec events occurring prior to "offset" time
        DrecEvent* nextEvent = (DrecEvent*)drec_event_list.Head();
        offset_pending = true;
        while (nextEvent)
        {
            if (nextEvent->GetTime() <= offset)
            {
                ProcessDrecEvent(*nextEvent);
            }
            else
            {
                double currentTime = offset > 0.0 ? offset : 0.0;
                drec_event_timer.SetInterval(nextEvent->GetTime() - currentTime);
                timer_mgr.ActivateTimer(drec_event_timer);
                break;  
            }
            nextEvent = (DrecEvent*)nextEvent->Next();
        }
        offset_pending = false;
        next_drec_event = nextEvent;

        // Activate any group joins deferred during "offset" processing.
        drec_group_list.JoinDeferredGroups(*this);
    }
    else  // Schedule absolute start time
    {
        // Make sure there are pending events
        //if(!drec_event_list.IsEmpty() || !flow_list.IsEmpty()) 
        {
            // Calculate start time delta and schedule start_timer 
            // (can delay start up to 12 hours into future)
#ifdef _WIN32_WCE
            DMSG(0, "Mgen::Start() warning: WinCE absolute start time might not be correct!\n");
            UINT32 hours = currentTime.tv_sec / 3600;
            UINT32 minutes = (currentTime.tv_sec - (3600*hours)) / 60;
            UINT32 seconds = currentTime.tv_sec - (3600*hours) - (60*minutes);
            hours = hours % 24;
            double nowSec = hours*3600 + minutes*60 + seconds + 1.0e-06*((double)currentTime.tv_usec);   
#else
            struct tm now;
            if (start_gmt)
                memcpy(&now, gmtime((time_t*)&currentTime.tv_sec), sizeof(struct tm));
            else
                memcpy(&now, localtime((time_t*)&currentTime.tv_sec), sizeof(struct tm));
            double nowSec = now.tm_hour*3600 + now.tm_min*60 + now.tm_sec + 1.0e-06*((double)currentTime.tv_usec);
#endif // if/else _WIN32_WCE
            double startSec = start_hour*3600 + start_min*60 + start_sec;
            double delta = startSec - nowSec;
            if (delta < 0.0) delta += (24*3600);
            if (delta > (12*3600))
            {
                DMSG(0, "Mgen::Start() Error: Specified start time has already elapsed\n");
                return false;   
            }
            if (start_timer.IsActive()) start_timer.Deactivate();

            start_timer.SetInterval(delta);
            timer_mgr.ActivateTimer(start_timer); 
        }
    }

    started = true; 
    return true;
}  // end Mgen::Start()

MgenTransport* Mgen::JoinGroup(const ProtoAddress&   groupAddress, 
                               const ProtoAddress&   sourceAddress,
                               const char*           interfaceName,
                               UINT16        thePort)
{
    
#ifndef IP_MAX_MEMBERSHIPS
#ifdef WIN32
    // WIN32 allows one IP multicast membership per socket
#define IP_MAX_MEMBERSHIPS 1
#else
    // Solaris (and perhaps other Unix) have no 
    // pre-defined limit on group memberhips
#define IP_MAX_MEMBERSHIPS -1    
#endif // if/else WIN32  
#endif // !IP_MAX_MEMBERSHIPS
    
#ifdef SIMULATE
#ifdef IP_MAX_MEMBERSHIPS
#undef IP_MAX_MEMBERSHIPS
#endif // IP_MAX_MEMBERSHIPS
#define IP_MAX_MEMBERSHIPS -1
#endif // SIMULATE
    
    // Sockets with space to join are at top of list
    // find socket of approprate ProtoAddress::Type 

    bool newTransport = false;
    MgenTransport* mgenTransport = FindTransportByInterface(interfaceName, thePort,groupAddress.GetType());
    
    if (!mgenTransport || 
        ((IP_MAX_MEMBERSHIPS > 0) &&
         (mgenTransport->GroupCount() >= (unsigned int)IP_MAX_MEMBERSHIPS)))
    {
        // Create new "dummy" socket for group joins only
        ProtoAddress tmpAddress;
        if (!(mgenTransport = GetMgenTransport(UDP,thePort,tmpAddress,true)))
        {
            DMSG(0, "Mgen::JoinGroup() memory allocation error: %s\n",
                 GetErrorString());
            return NULL;
        }
        newTransport = true;
    } 
    if (mgenTransport->JoinGroup(groupAddress, sourceAddress, interfaceName))
    {
        if ((IP_MAX_MEMBERSHIPS > 0) &&
            (mgenTransport->GroupCount() >= (unsigned int)IP_MAX_MEMBERSHIPS))
        {
            transport_list.Remove(mgenTransport);  // move "full" group socket to end of list
            transport_list.Append(mgenTransport);   
        }
        return mgenTransport;   
    }
    else
    {
        if (newTransport)
        {
            transport_list.Remove(mgenTransport);
            delete(mgenTransport);
        }
        return (MgenTransport*)NULL;   
    }
}  // end Mgen::JoinGroup()

bool Mgen::LeaveGroup(MgenTransport* mgenTransport,
                      const ProtoAddress& groupAddress, 
		      const ProtoAddress& sourceAddress,
                      const char*           interfaceName)
{
  if (mgenTransport->LeaveGroup(groupAddress, sourceAddress, interfaceName))
    {
        transport_list.Remove(mgenTransport);
        transport_list.Prepend(mgenTransport);  
        return true; 
    }
    else
    {
        return false;
    }
}  // end Mgen::LeaveGroup()

/**
 * This finds us a mgenTransport with matching attributes  
 * If a valid mgenTransport ptr is passed, we get the next 
 * match in the list
 */
MgenTransport* Mgen::FindMgenTransport(Protocol theProtocol,
                                       UINT16 srcPort,
                                       const ProtoAddress& dstAddress,
                                       bool closedOnly,
                                       MgenTransport* mgenTransport)
{ 
    MgenTransport* next;
    if (!mgenTransport) {next = transport_list.head;}
    else {next = mgenTransport->next;}
    
    while (next)
    {
        // Same protocol and srcPort?
      if ((next->GetProtocol() == theProtocol) &&
          (srcPort == 0 || next->srcPort == srcPort) &&

            // ignore events && unconnected udp sockets
            // have invalid addrs and should match
            (((!dstAddress.IsValid() &&
	       !next->dstAddress.IsValid()) && theProtocol == UDP)
	     ||
	     // For tcp ignore events we want to close the
	     // listening socket too which won't have a dst address
	     (theProtocol == TCP && !dstAddress.IsValid())
             || 
             (dstAddress.IsValid() && 
              next->dstAddress.IsEqual(dstAddress)))
            
            // We should only have one sink
            || ((next->GetProtocol() == SINK && theProtocol == SINK)))
        {
	  if (!closedOnly) { return next; }
	  if (!next->IsOpen())  { return next;}


        }
        next = next->next;   
    }

    return (MgenTransport*)NULL;
    
}  // end MgenTransport::FindMgenTransport()

/**
 * This search finds an mgenTransport suitable for joining a multicast group
 * We want a matching multicast interface name & type, and a matching port number
 * if "port" is non-zero
 */
MgenTransport* Mgen::FindTransportByInterface(const char*           interfaceName, 
                                              UINT16        thePort,
                                              ProtoAddress::Type addrType)
{

    MgenTransport* nextTransport = transport_list.head;
    while (nextTransport)
    {
        if (nextTransport->GetAddressType() != addrType)
        {
            nextTransport = nextTransport->next;   
            continue;
        }
      
        if (nextTransport->GetProtocol() == UDP)
        {
            MgenUdpTransport* next = static_cast<MgenUdpTransport*>(nextTransport);
            // If "thePort" is non-zero we must a socket of same port
            if (0 != thePort)               
            {
                if (thePort == next->srcPort)
                {
                    // OK, protocol and port matches, so ...
                    if (NULL != interfaceName)
                    {
                        if ((NULL == next->GetMulticastInterface()) && // unspecified interface &&
                            (0 == next->GroupCount()))           // no joins yet, so we'll 
                        {                                       // call it a "match"
                            next->SetMulticastInterface(interfaceName);
                            return nextTransport;
                        }
                        else
                        {
                            if ((next->GetMulticastInterface() && interfaceName) &&
                                !strncmp(next->GetMulticastInterface(), interfaceName, 16))
                            {
                                return nextTransport;  // it's a "match"
                            }
                            else
                            {
                                DMSG(0, "Mgen::FindMgenTransportByInterface() matching port but interface mismatch!\n");
                                return NULL;
                            }
                        }
                    }
                    else
                    {
                        if (0 == next->GroupCount())
                        {
                            next->SetMulticastInterface('\0');
                            return nextTransport;
                        }
                        else
                        {
                            if (NULL == next->GetMulticastInterface())
                              return nextTransport;
                            else
                            {
                                DMSG(0, "Mgen::FindMgenTransportByInterface() matching port but interface mismatch!\n");
                                return NULL;
                            }
                        }
                    }
                }
            }
            else
            {
               bool match = interfaceName && next->GetMulticastInterface() ? (0 == strncmp(interfaceName, next->GetMulticastInterface(), 16)) :
                  (NULL == next->GetMulticastInterface());
                if (match) return nextTransport;
                
            }
        }
        nextTransport = nextTransport->next;   
    }
    return (MgenTransport*)NULL;
}  // end Mgen::FindTransportByInterface()

MgenTransport* Mgen::FindMgenTransportBySocket(const ProtoSocket& socket)
{
    MgenTransport* next = transport_list.head;
    while (next)
    {
        if (next->OwnsSocket(socket)) 
          return next;
        next = next->next;   
    }
    return (MgenTransport*)NULL;
}  // end Mgen::FindMgenTransportBySocket()


/**
 * @note We get a transport with a matching dstAddress for connected udp sockets
 * The set dst address should not be set for unconnected udp sockets as multiple
 * destinations can share the same socket
 */
MgenTransport* Mgen::GetMgenTransport(Protocol theProtocol,
                                      UINT16 srcPort,
                                      const ProtoAddress& dstAddress,
                                      bool closedOnly,
                                      bool connect)
{
    ProtoAddress tmpAddress;
    if (connect || theProtocol == TCP ) 
      tmpAddress = dstAddress;// ignore events have invalid addrs

    MgenTransport* theTransport =  FindMgenTransport(theProtocol,srcPort,tmpAddress,closedOnly,NULL);      

    if (theTransport)
      return theTransport;
    
    switch (theProtocol)
    {
    case UDP:
    case TCP:
      {
          MgenSocketTransport* theTransport;

          if (theProtocol == UDP)
            theTransport = new MgenUdpTransport(*this,theProtocol,srcPort,tmpAddress);
          else
            theTransport = new MgenTcpTransport(*this,theProtocol,srcPort,dstAddress);
          if (!theTransport)
          {
              DMSG(0, "Mgen::GetMgenTransport() mgen transport allocation error: %s\n",
                   GetErrorString());
              return NULL; 
          }

          transport_list.Prepend(theTransport);          
          return theTransport;

          break;
      }
    case SINK:
    case SOURCE:
      {          
          MgenSinkTransport* theTransport = MgenSinkTransport::Create(*this, theProtocol);

          if (!theTransport)
          {
              DMSG(0, "Mgen::GetMgenTransport() mgen transport allocation error: %s\n",
                   GetErrorString());
              return NULL; 
          }
          // ljt set these in constructor's too?
          if (SINK == theProtocol)
            theTransport->SetPath(sink_path);
          else
            theTransport->SetPath(source_path);
          theTransport->SetSinkBlocking(sink_non_blocking);
          transport_list.Prepend(theTransport);          
          return theTransport;
          
          break;
      }
    default:
      DMSG(0,"Mgen::GetMgenTransport() Error: Invalid protocol specified.\n");
      return NULL;
    }        

    return NULL;
} // end Mgen::GetMgenTransport


void Mgen::Stop()
{
    if (started)
    {
        if (NULL != log_file) 
        {
            // Log STOP event
            struct timeval currentTime;
            ProtoSystemTime(currentTime);
            if (log_binary)
            {
                    unsigned int index = 0;
                    char buffer[128];
                    buffer[index++] = (char)STOP_EVENT;
                    // zero "reserved" field
                    buffer[index++] = 0;
                    // set "recordLength" field
                    UINT16 temp16 = htons(8);
                    memcpy(buffer+index, &temp16, sizeof(INT16));
                    index += sizeof(INT16);
                    // set "eventTime" fields
                    UINT32 temp32 = htonl(currentTime.tv_sec);
                    memcpy(buffer+index, &temp32, sizeof(INT32));
                    index += sizeof(INT32);
                    temp32 = htonl(currentTime.tv_usec);
                    memcpy(buffer+index, &temp32, sizeof(INT32));
                    index += sizeof(INT32);
                    // write the record
                    if (fwrite(buffer, sizeof(char), index, log_file) < index)
                        DMSG(0, "Mgen::Stop() fwrite error: %s\n", GetErrorString());
            }
            else
            {
#ifdef _WIN32_WCE
                struct tm timeStruct;
                timeStruct.tm_hour = currentTime.tv_sec / 3600;
                UINT32 hourSecs = 3600 * timeStruct.tm_hour;
                timeStruct.tm_min = (currentTime.tv_sec - hourSecs) / 60;
                timeStruct.tm_sec = currentTime.tv_sec - hourSecs - (60*timeStruct.tm_min);
                timeStruct.tm_hour = timeStruct.tm_hour % 24;
                struct tm* timePtr = &timeStruct;
#else            
		struct tm* timePtr;
		if (local_time)
		  timePtr = localtime((time_t*)&currentTime.tv_sec);
		else
		  timePtr = gmtime((time_t*)&currentTime.tv_sec);

#endif // if/else _WIN32_WCE
                Mgen::Log(log_file, "%02d:%02d:%02d.%06lu STOP\n",
                                   timePtr->tm_hour, timePtr->tm_min, 
                                   timePtr->tm_sec, (UINT32)currentTime.tv_usec);
            }  // end if/else(log_binary)
            
        }  //end if(log_file)
        
        // Save current offset and pending flow sequence state
        if (save_path)
        {
            FILE* saveFile = fopen(save_path, "w+");
            if (saveFile)
            {
                flow_list.SaveFlowSequences(saveFile);
                Mgen::Log(saveFile, "OFFSET %f\n", GetCurrentOffset());
                fclose(saveFile);
            }
            else
            {
                DMSG(0, "Mgen::Stop() error opening save file: %s\n", GetErrorString());   
            }
        }
        started = false;
    } // end if (started)

    
    if (NULL != log_file)
    {
        fflush(log_file);
        if ((log_file != stdout) && (stderr != log_file))
        {
            fclose(log_file);
            log_file = NULL;   
        }
    }
    

    if (start_timer.IsActive()) start_timer.Deactivate();
    flow_list.Destroy();
    if (drec_event_timer.IsActive()) drec_event_timer.Deactivate();
    drec_event_list.Destroy();
    drec_group_list.Destroy(*this);
    transport_list.Destroy();
}  // end Mgen::Stop()

bool Mgen::OnStartTimeout(ProtoTimer& /*theTimer*/)
{
    start_sec = -1.0;
    Start();
    return true;   
}  // Mgen::OnStartTimeout()

bool Mgen::OnDrecEventTimeout(ProtoTimer& /*theTimer*/)
{    
    // 1) Process next pending drec event
    ASSERT(next_drec_event);
    ProcessDrecEvent(*next_drec_event);
    
    // 2) Install next DREC event timeout (or kill timer)
    double eventTime = next_drec_event->GetTime();
    next_drec_event = (DrecEvent*)next_drec_event->Next();
    if (next_drec_event)
    {
        double nextInterval = next_drec_event->GetTime() - eventTime;
        nextInterval = nextInterval > 0.0 ? nextInterval : 0.0;
        drec_event_timer.SetInterval(nextInterval);
        return true;
    }
    else
    {
        drec_event_timer.Deactivate();
        return false;
    }
}  // end Mgen::OnDrecEventTimeout()

/**
 * Process JOIN, LEAVE, IGNORE, LISTEN events
 */

void Mgen::ProcessDrecEvent(const DrecEvent& event)
{       
    // 1) Process the next DREC event
    switch (event.GetType())
    {
    case DrecEvent::JOIN:
      {
      const UINT16* port = event.GetPortList();
      UINT16 portCount = event.GetPortCount();

      // if no port given find first available socket to join
      // this is the legacy behavior (the preferred command
      // is to set the port on the join statement)
      if (portCount == 0)
      {
          if (!drec_group_list.JoinGroup(*this,
                                         event.GetGroupAddress(),
                                         event.GetSourceAddress(),
                                         event.GetInterface(),
                                         event.GetGroupPort(),
                                         offset_pending))
          {
              DMSG(0, "Mgen::ProcessDrecEvent(JOIN) Warning: error joining group\n");
          }	  
          else
          {
              MgenMsg theMsg; 
              theMsg.LogDrecEvent(JOIN_EVENT, &event, event.GetGroupPort(),*this);
          } 
      }
      else 
      {
          for (UINT16 i = 0; i < portCount; i++)
          {
              if (!drec_group_list.JoinGroup(*this,
                                             event.GetGroupAddress(),
                                             event.GetSourceAddress(),
                                             event.GetInterface(),
                                             port[i],
                                             offset_pending))
              {
                  DMSG(0,"Mgen::ProcessDrecEvent(JOIN) Warning: error joining group\n");
              }
              else
              {
                  MgenMsg theMsg;
                  theMsg.LogDrecEvent(JOIN_EVENT,&event, port[i], *this);
              }
          }
      }
      }
      
      break;
      
    case DrecEvent::LEAVE:
    {
        const UINT16* port = event.GetPortList();
        UINT16 portCount = event.GetPortCount();

        if (portCount == 0)
        {
            if (!drec_group_list.LeaveGroup(*this,
                                            event.GetGroupAddress(),
                                            event.GetSourceAddress(),
                                            event.GetInterface(),
                                            event.GetGroupPort()))
            {
                DMSG(0, "Mgen::ProcessDrecEvent(LEAVE) Warning: error leaving group\n");
            }
            else
            {
                MgenMsg theMsg;
                theMsg.LogDrecEvent(LEAVE_EVENT, &event, event.GetGroupPort(),*this);
            }
        } else
        {
            for (UINT16 i = 0; i < portCount; i++)
            {
                if (!drec_group_list.LeaveGroup(*this,
                                                event.GetGroupAddress(),
                                                event.GetSourceAddress(),
                                                event.GetInterface(),
                                                port[i]))
                    {
                        DMSG(0,"Mgen::ProcessDrecEvent(LEAVE) warning: error leaving group\n");
                    }
                    else
                    {
                        MgenMsg theMsg;
                        theMsg.LogDrecEvent(LEAVE_EVENT, &event, port[i] ,*this);
                    }
            }
        }
    }
        break;
      
    case DrecEvent::LISTEN:
      {
          const UINT16* port = event.GetPortList();
          UINT16 portCount = event.GetPortCount();
          for (UINT16 i = 0; i < portCount; i++)
          {
              
              ProtoAddress tmpAddress;
              MgenTransport* theMgenTransport = GetMgenTransport(event.GetProtocol(),port[i],tmpAddress);
              if (theMgenTransport)
                
              {
                  // Listen increments reference count
                  if (!theMgenTransport->Listen(port[i],addr_type,true))
                    continue;
                  
              }
              else
              {
                  DMSG(0, "Mgen::ProcessDrecEvent(LISTEN) Error: no socket available\n");
                  continue;
              }
              // If a socket Rx buffer size is specified
              unsigned int rxBufferSize = event.GetRxBuffer();
              if (0 != rxBufferSize)
              {
                  if (!theMgenTransport->SetRxBufferSize(rxBufferSize))
                    DMSG(0, "Mgen::ProcessDrecEvent(LISTEN) error setting socket rx buffer\n");  
              }  
              MgenMsg theMsg;
              theMsg.LogDrecEvent(LISTEN_EVENT, &event, port[i],*this);
          }
      }
      break;            
      
    case DrecEvent::IGNORE_:
      {
          Protocol theProtocol;
          switch (event.GetProtocol())
          {
          case UDP:
            theProtocol = UDP;
            break; 
          case TCP:
            theProtocol = TCP;
            break;  
          default:
            DMSG(0, "Mgen::ProcessDrecEvent(LISTEN) invalid protocol\n");
            return;
          } 
          const UINT16* port = event.GetPortList();
          UINT16 portCount = event.GetPortCount();
          for (UINT16 i = 0; i < portCount; i++)
          {
              
              /* Delete all sockets associated with the proto/port. */
              /* (regardless of source port)                        */
              
              MgenTransport* mgenTransport;
              MgenTransport* next = NULL;
              ProtoAddress tmpAddress;
              while ((mgenTransport = FindMgenTransport(theProtocol, port[i],tmpAddress,false,next)))
              {
                  if (mgenTransport->HasListener()) 
                  {
                      // Disable "listen"
                      mgenTransport->StopInputNotification(); 
                      if (mgenTransport->IsOpen())
                      {
                          // When we get an IGNORE event, close the
                          // listening socket right away, but shutdown
                          // any accepted connections.
                          MgenMsg theMsg;
                          if (mgenTransport->IsListening())
                          {
                              mgenTransport->Close();
                              theMsg.LogDrecEvent(IGNORE_EVENT, &event, port[i], *this);
                          }
                          else
                          {
                              if (mgenTransport->Shutdown())
                              {
                                struct timeval currentTime;
                                ProtoSystemTime(currentTime);
                                mgenTransport->LogEvent(SHUTDOWN_EVENT,&theMsg,currentTime);
                              }
                          }
                     }
                  }  
                  else 
                  {
                      DMSG(0,"Mgen::ProcessDrecEvent(IGNORE) Error: no socket on port %hu\n",port[i]);
                  }
                  next = mgenTransport;
              }
          }
          break;
      }
      
    case DrecEvent::INVALID_TYPE:
      ASSERT(0);
      break; 
    }  // end switch(event.GetType())
}  // end Mgen::ProcessDrecEvent()


bool Mgen::OpenLog(const char* path, bool append, bool binary)
{
    if (append)
    {
        
#ifdef WIN32
        WIN32_FILE_ATTRIBUTE_DATA fileAttr;
#ifdef _UNICODE
        wchar_t wideBuffer[PATH_MAX];
        int pathLen = strlen(path);
        if (pathLen > PATH_MAX) 
          pathLen = PATH_MAX;
        else
          pathLen += 1;
        mbstowcs(wideBuffer, path, pathLen);
        LPCTSTR pathPtr = wideBuffer;
#else
        LPCTSTR pathPtr = path;
#endif // if/else _UNICODE
        if (0 == GetFileAttributesEx(pathPtr, GetFileExInfoStandard, &fileAttr))
          log_empty = true;  // error -- assume (nonexistent) file is empty
        else if ((0 == fileAttr.nFileSizeLow) && (0 == fileAttr.nFileSizeHigh))
          log_empty = true;
        else
          log_empty = false;
#else
        struct stat buf;
        if (stat(path, &buf))   // zero return value == success
          log_empty = true;  // error -- assume file is empty
        else if (buf.st_size == 0)
          log_empty = true;
        else
          log_empty = false;
#endif  // if/else WIN32/UNIX
        
    }
    else
    {
        log_empty = true;   
    }
    const char* mode;
    if (binary)
    {
        mode = append ? "ab" : "wb+";
        log_binary = true;
    }
    else
    {
        mode = append ? "a" : "w+";
        log_binary = false;
    }
    FILE* logFile = fopen(path, mode);
    if (!logFile)
    {
        DMSG(0, "Mgen::OpenLog() fopen() Error: %s\n", GetErrorString());   
        return false;    
    }   
    SetLogFile(logFile);
    log_open = true;
    return true;
}  // end Mgen::OpenLog()

void Mgen::SetLogFile(FILE* filePtr)
{
    CloseLog();
    log_file = filePtr;
#ifdef _WIN32_WCE
    if ((stdout == log_file) || (stderr == log_file))
        Log = Mgen::LogToDebug;
    else
        Log = fprintf;
#endif // _WIN32_WCEgoto

}  // end Mgen::SetLogFile()

void Mgen::CloseLog()
{
    if (log_file)
    {
        if ((stdout != log_file) && (stderr != log_file))
            fclose(log_file);
        log_file = NULL;
    }
}  // end Mgen::CloseLog()

/**
 * Query flow_list and drec_event_list for an idea
 * of the current (or greatest) estimate of 
 * current relative script time offset
 */
double Mgen::GetCurrentOffset() const
{
    if (!started) 
        return -1.0;
    if (next_drec_event)
        return (next_drec_event->GetTime() - drec_event_timer.GetTimeRemaining());
    const DrecEvent* lastEvent = (const DrecEvent*)drec_event_list.Tail();
    double drecOffset = lastEvent ? lastEvent->GetTime() : -1.0;
    double mgenOffset = flow_list.GetCurrentOffset();
    return (drecOffset > mgenOffset) ? drecOffset : mgenOffset;   
}  // end Mgen::GetCurrentOffset()

/**
 * Parse an MGEN script
 */
bool Mgen::ParseScript(const char* path)
{
    // Open script file
    FILE* scriptFile = fopen(path, "r");
    if (!scriptFile)
    {
        DMSG(0, "Mgen::ParseScript() fopen() Error: %s\n", GetErrorString());   
        return false;
    }
    
    // Read script file line by line using FastReader
    FastReader reader;
    unsigned int lineCount = 0;
    unsigned int lines = 0;
    while (1)
    {
        lineCount += lines;  // for grouped (continued) lines
        char lineBuffer[SCRIPT_LINE_MAX+1];
        unsigned int len = SCRIPT_LINE_MAX;
        switch (reader.ReadlineContinue(scriptFile, lineBuffer, &len, &lines))
        {
            case FastReader::OK:
                lineCount++;
                lines--;
                break;
            case FastReader::DONE:
                fclose(scriptFile);
                return true; // done with script file
            case FastReader::ERROR_:
                DMSG(0, "Mgen::ParseScript() Error: script file read error\n");
                fclose(scriptFile);
                return false;
        }
        if (!ParseEvent(lineBuffer, lineCount))
        {
            DMSG(0, "Mgen::ParseScript() Error: invalid mgen script line: %lu\n", 
                    lineCount);
            fclose(scriptFile);
            return false;   
        }
    }  // end while (1)
    return true;
}  // end Mgen::ParseScript()

bool Mgen::ParseEvent(const char* lineBuffer, unsigned int lineCount)
{
    const char *ptr = lineBuffer;
    // Strip leading white space
    while ((' ' == *ptr) || ('\t' == *ptr)) ptr++;
    // Check for comment line (leading '#')
    if ('#' == *ptr) return true;
    char fieldBuffer[SCRIPT_LINE_MAX+1];
    // Script lines are in form {<globalCmd>|<eventTime>} ...
    if (1 != sscanf(ptr, "%s", fieldBuffer))
    {
        // Blank line?
        return true;   
    }
    // Set ptr to next field in line, stripping white space
    ptr += strlen(fieldBuffer);
    while ((' ' == *ptr) || ('\t' == *ptr)) ptr++;
    
    Command cmd = GetCommandFromString(fieldBuffer);
    if (EVENT == cmd)
    {
        // read in <eventTime> or <eventType>
        if (1 != sscanf(ptr, "%s", fieldBuffer))
        {
            DMSG(0, "Mgen::ParseEvent() Error: empty EVENT command at line: %lu\n", lineCount);
            return false;    
        }
        // Set ptr to next field in line, stripping white space
        ptr += strlen(fieldBuffer);
        while ((' ' == *ptr) || ('\t' == *ptr)) ptr++;          
    }
    
    // If it's not a "global command", assume it's an event.
    if (INVALID_COMMAND == cmd) cmd = EVENT;
    
    
    switch (cmd)
    {
    case EVENT:
      {
          // EVENT line can begin with the <eventTime> _or_ the <eventType>
          // for implicit, immediate events.
          double eventTime;
          if (1 == sscanf(fieldBuffer, "%lf", &eventTime))
          {
              // Read event command
              if (1 != sscanf(ptr, "%s", fieldBuffer))
              {
                  DMSG(0, "Mgen::ParseEvent() Error: missing command at line: %lu\n", lineCount);
                  return false;   
              }
              // Set ptr to next field in line, stripping white space
              ptr += strlen(fieldBuffer);
              while ((' ' == *ptr) || ('\t' == *ptr)) ptr++;
          }
          else
          {
              eventTime = -1.0;   
          }
          
          // Is it an MgenEvent or a DrecEvent?
          if (MgenEvent::INVALID_TYPE != MgenEvent::GetTypeFromString(fieldBuffer))
          {
              // It's an MGEN event   
              // 1) Read the flow id
              if (1 != sscanf(ptr, "%s", fieldBuffer))
              {
                  DMSG(0, "Mgen::ParseEvent() Error: missing <flowId> at line: %lu\n", lineCount);
                  return false;
              }
              unsigned long flowId;
              if (1 != sscanf(fieldBuffer, "%lu", &flowId))
              {
                  DMSG(0, "Mgen::ParseEvent() Error: invalid <flowId> at line: %lu\n", lineCount);
                  return false;
              }
              
              // 2) Find the flow
              MgenFlow* theFlow = flow_list.FindFlowById(flowId);
              if (!theFlow)
              {
                  if (!(theFlow = new MgenFlow(flowId, 
                                               timer_mgr, 
                                               controller,
                                               *this,
                                               default_queue_limit,
                                               default_flow_label)))
                  {
                      DMSG(0, "Mgen::ParseEvent() Error: MgenFlow memory allocation error: %s\n",
                           GetErrorString());   
                      return false;
                  }
                  theFlow->SetPositionCallback(get_position, get_position_data);
#ifdef HAVE_GPS
                  theFlow->SetPayloadHandle(payload_handle);
#endif // HAVE_GPS
                  flow_list.Append(theFlow);
              }
              
              // 3) Create event object
              MgenEvent* theEvent = new MgenEvent();
              if (!theEvent)
              {
                  DMSG(0, "MgenFlow::ParseEvent() mgen event allocation error: %s\n",
                       GetErrorString());
                  return false; 
              }
              if (!theEvent->InitFromString(lineBuffer))
              {
                  DMSG(0, "MgenFlow::ParseEvent() event init error\n");
                  delete theEvent;
                  return false; 
              }
              // Update host_addr port now that we know it
              if (host_addr.IsValid() && theEvent->GetSrcPort() != 0)
              { 
                  host_addr.SetPort(theEvent->GetSrcPort());
                  //   theFlow->SetHostAddress(host_addr);
              }
              // Update flow specific queue limit if specified
              if (theEvent->GetQueueLimit())
                theFlow->SetQueueLimit(theEvent->GetQueueLimit());
              bool reallyStarted = (started && !start_timer.IsActive());
              double currentTime =  reallyStarted ? GetCurrentOffset() : 0.0;
              if (currentTime < 0.0) currentTime = 0.0;
              if (!theFlow->InsertEvent(theEvent, reallyStarted, currentTime))
              {
                  DMSG(0, "Mgen::ParseEvent() Error: invalid mgen script line: %lu\n", lineCount);
                  delete theEvent;
                  return false;
              }
          }  // End MGEN event processing
          else if (DrecEvent::INVALID_TYPE != DrecEvent::GetTypeFromString(fieldBuffer))
          {
              // It's a DREC event
              DrecEvent* theEvent = new DrecEvent();
              if (!theEvent)
              {
                  DMSG(0, "MgenFlow::ParseEvent() drecevent allocation error: %s\n",
                       GetErrorString());
                  return false; 
              }
              
              
              if (!theEvent->InitFromString(lineBuffer))
              {
                  DMSG(0, "Mgen::ParseEvent() Error: invalid mgen script line: %lu\n", lineCount);
                  delete theEvent;
                  return false;   
              }
              theEvent->SetTime(eventTime);
              InsertDrecEvent(theEvent);
          }   
          else
          {
              DMSG(0, "Mgen::ParseEvent() Error: invalid command \"%s\" at line: %lu\n", 
                   fieldBuffer, lineCount);
              return false;
          }
          break;
      }
      
    default:
      // Is it a global command
      if (INVALID_COMMAND != cmd)
      {
          if (!OnCommand(cmd, ptr))
          {
              DMSG(0, "Mgen::ParseEvent() Error: bad global command at line: %lu\n", lineCount);
              return false;
          }
      }
      else
      {
          DMSG(0, "Mgen::ParseEvent() Error: invalid command at line: %lu\n", lineCount);
          return false;
      }
      break;
    }
    return true;
}  // end Mgen::ParseEvent()

void Mgen::InsertDrecEvent(DrecEvent* theEvent)
{
    double eventTime = theEvent->GetTime();
    if (started && !start_timer.IsActive())
    {
        double currentTime = GetCurrentOffset();
        if (currentTime < 0.0) currentTime = 0.0;
        if (eventTime < currentTime)
        {
            theEvent->SetTime(currentTime);
            drec_event_list.Precede(next_drec_event, theEvent);
            ProcessDrecEvent(*theEvent);
        }
        else
        {
            theEvent->SetTime(eventTime);
            drec_event_list.Insert(theEvent);
            // Reschedule next drec timeout if needed
            if (drec_event_timer.IsActive())
            {
                double nextTime = next_drec_event->GetTime();
                if (eventTime < nextTime)
                {
                    next_drec_event = theEvent;
                    drec_event_timer.SetInterval(eventTime - currentTime);
                    drec_event_timer.Reschedule();
                }
            }
            else
            {
                next_drec_event = theEvent;
                drec_event_timer.SetInterval(eventTime - currentTime);
                timer_mgr.ActivateTimer(drec_event_timer);
            }
        }
    }   
    else
    {
        eventTime = eventTime > 0.0 ?  eventTime : 0.0;
        theEvent->SetTime(eventTime);
        drec_event_list.Insert(theEvent);
    }
}  // end Mgen::InsertDrecEvent()

// Global command processing
const StringMapper Mgen::COMMAND_LIST[] =
{
    {"+EVENT",      EVENT},
    {"+START",      START},
    {"+INPUT",      INPUT},
    {"+OUTPUT",     OUTPUT},
    {"+LOG",        LOG},
    {"+SAVE",       SAVE},
    {"+DEBUG",      DEBUG},
    {"+OFFSET",     OFFSET},
    {"-TXLOG",	    TXLOG},
    {"-LOCALTIME",  LOCALTIME},
    {"-NOLOG",      NOLOG},
    {"+DLOG",       DLOG},
    {"-BINARY",     BINARY},
    {"-FLUSH",      FLUSH},
    {"+LABEL",      LABEL},
    {"+RXBUFFER",   RXBUFFER},
    {"+TXBUFFER",   TXBUFFER},
    {"+BROADCAST",  BROADCAST},
    {"+TOS",        TOS},
    {"+TTL",        TTL},
    {"+INTERFACE",  INTERFACE},
    {"-CHECKSUM",   CHECKSUM},
    {"-TXCHECKSUM", TXCHECKSUM},
    {"-RXCHECKSUM", RXCHECKSUM},
    {"+QUEUE",      QUEUE},
    {"+REUSE",      REUSE},
    {"+OFF",        INVALID_COMMAND},  // to deconflict "offset" from "off" event
    {NULL,          INVALID_COMMAND}   
};

const char* Mgen::GetCmdName(Command cmd)
{
    const StringMapper* m = COMMAND_LIST;
    while (NULL != m->string)
    {
        if ((Command)(m->key) == cmd)
        {
            if (NULL != m->string)
                return m->string + 1;
            else
                return NULL;
        }
    }
    return NULL;
}  // end Mgen::GetCmdName()

Mgen::Command Mgen::GetCommandFromString(const char* string)
{
    // Make comparison case-insensitive
    char upperString[16];
    unsigned int len = strlen(string);

    len = len < 16 ? len : 16;
    
    for (unsigned int i = 0 ; i < len; i++)
        upperString[i] = toupper(string[i]);
    const StringMapper* m = COMMAND_LIST;
    Mgen::Command theCommand = INVALID_COMMAND;
    while (NULL != (*m).string)
    {
        if (!strncmp(upperString, (*m).string+1, len))
            theCommand = ((Command)((*m).key));
        m++;
    }
    return theCommand;
}  // end Mgen::GetCommandFromString()

// This tells if the command is valid and whether args are expected
Mgen::CmdType Mgen::GetCmdType(const char* cmd)
{
    if (!cmd) return CMD_INVALID;
    char upperCmd[32];  // all commands < 32 characters
    unsigned int len = strlen(cmd);
    len = len < 31 ? len : 31;
    unsigned int i;
    for (i = 0; i < (len + 1); i++)
        upperCmd[i] = toupper(cmd[i]);
    
    bool matched = false;
    const StringMapper* m = COMMAND_LIST;
    CmdType type = CMD_INVALID;
    while (INVALID_COMMAND != (*m).key)
    {
        if (!strncmp(upperCmd, (*m).string+1, len))
        {
            if (matched)
            {
                // ambiguous command (command should match only once)
                return CMD_INVALID;
            }
            else
            {
                matched = true;   
                if ('+' == (*m).string[0])
                    type = CMD_ARG;
                else
                    type = CMD_NOARG;
            }
        }
        m++;
    }
    return type; 
}  // end Mgen::GetCmdType()

bool Mgen::OnCommand(Mgen::Command cmd, const char* arg, bool override)
{ 

    switch (cmd)
    {
    case START:
      {
          if (!arg)
          {
              DMSG(0, "Mgen::OnCommand() Error: missing <startTime>\n");
              return false;   
          }
          if (override || !start_time_lock)
          {
              // convert to upper case for case-insensitivity
              // search for "GMT" or "NOW" keywords
              char temp[32];
              unsigned int len = strlen(arg);
              len = len < 31 ? len : 31;
              unsigned int i;
              for (i = 0 ; i < len; i++)
                temp[i] = toupper(arg[i]);
              temp[i] = '\0';
              unsigned int startHour, startMin;
              double startSec;
              // arg should be "hr:min:sec[GMT]" or "NOW"
              if (3 == sscanf(temp, "%u:%u:%lf", &startHour, &startMin, &startSec))
              {
                  start_hour = startHour;
                  start_min = startMin;
                  start_sec = startSec;
                  if (strstr(temp, "GMT"))
                    start_gmt = true;
                  else
                    start_gmt = false;
                  if (start_hour == 0 && start_min == 0 && start_sec == 0)
                    start_sec = -1.0;
              }
              else
              {
                  // Check for "NOW" keywork (case-insensitive)
                  if (strstr(temp, "NOW"))
                  {
                      // negative start_time_sec indicates immediate start
                      start_sec = -1.0; 
                  }
                  else
                  {
                      DMSG(0, "Mgen::OnCommand() Error: invalid START time\n");
                      return false;
                  }
              }
              start_time_lock = override;  // record START command precedence
              if (started) Start();  // Adamson reschedule any prior START directive
          }  // end if (override || !start_time_lock)
          break;
      }  // end case START
      
    case EVENT:
      if (!ParseEvent(arg, 0))
      {
          DMSG(0, "Mgen::OnCommand() - error parsing event\n");
          return false;
      }
      break;
      
    case INPUT:
      if (!ParseScript(arg))
      {
          DMSG(0, "Mgen::OnCommand() - error parsing script\n");
          return false;
      }
      break;
      
    case TXBUFFER:	
      unsigned int sizeTemp; 
      if (1 != sscanf(arg, "%u", &sizeTemp))
      {
          DMSG(0, "Mgen::OnCommand() - invalid tx buffer size\n");
          return false;
      }
      SetDefaultTxBufferSize(sizeTemp, override);
      break;            
    case RXBUFFER:
      {
          unsigned int sizeTemp; 
          if (1 != sscanf(arg, "%u", &sizeTemp))
          {
              DMSG(0, "Mgen::OnCommand() - invalid rx buffer size\n");
              
          }
          SetDefaultRxBufferSize(sizeTemp, override);
          break;
      }      
    case BROADCAST:
      if (!arg)
      {
          DMSG(0, "Mgen::OnCommand() Error: missing argument to BROADCAST\n");
          return false;   
      }
      {
          bool broadcastTemp;
          // convert to upper case for case-insensitivity
          char temp[4];
          unsigned int len = strlen(arg);
          len = len < 4 ? len : 4;
          unsigned int i;
          for (i = 0 ; i < len; i++)
            temp[i] = toupper(arg[i]);
          temp[i] = '\0';
          if(!strncmp("ON", temp, len))
              broadcastTemp = true;
          else if(!strncmp("OFF", temp, len))
              broadcastTemp = false;
          else
          {
              DMSG(0, "Mgen::OnCommand() Error: wrong argument to BROADCAST: %s\n", arg);
              return false;   
          }
          SetDefaultBroadcast(broadcastTemp, override);
          break;
      }

    case TOS:	
      {
          int tosTemp; 
          int result = sscanf(arg, "%i", &tosTemp);
          if ((1 != result) || (tosTemp < 0) || (tosTemp > 255))
          {                
              DMSG(0, "Mgen::OnCommand() - invalid tos value");
              return false;
          }            
          SetDefaultTos(tosTemp, override);
          break;
      }
      
    case INTERFACE:
      SetDefaultMulticastInterface(arg, override);
      break;
      
    case TTL:	
      {
          int ttlTemp;
          int result = sscanf(arg, "%i", &ttlTemp);
          if ((1 != result) || (ttlTemp < 0) || (ttlTemp > 255))
          {
              DMSG(0, "Mgen::OnCommand() - invalid ttl value");
              return false;
          }
          
          SetDefaultTtl(ttlTemp, override);
          break;
      }
      
    case LABEL:
      // (TBD) make flow_list.SetDefaultFlowLabel()
      if (override || !default_label_lock)
      {
          int tempLabel;
          if (1 != sscanf(arg, "%i", &tempLabel))
          {
              DMSG(0, "Mgen::OnCommand() - invalid flow label value");
              return false;
          }
          default_flow_label = tempLabel;
          default_label_lock = override; 
      }
      break;
      
    case OUTPUT:
      if (override || !log_file_lock)
      {
          if (!OpenLog(arg, false, log_binary))
          {
              DMSG(0, "Mgen::OnCommand() Error: log file open error: %s\n",
                   GetErrorString());
              return false;
          }
          log_file_lock = override;
      }
      break;
      
    case LOG:
      if (override || !log_file_lock)
      {
          if (!OpenLog(arg, true, log_binary))
          {
              DMSG(0, "Mgen::OnCommand() Error: log file open error: %s\n",
                   GetErrorString());
              return false;
          }
          log_file_lock = override;
      }
      break;
      
    case NOLOG:
      if (override || !log_file_lock)
      {
          SetLogFile(NULL);
          log_file_lock = override;
      }
      break;
      
    case DLOG:
      if (override)
      {
          if (!OpenDebugLog(arg))
          {
              DMSG(0, "Mgen::OnCommand(dlog) error opening debug log file: %s\n",
                   GetErrorString());
              return false;
          }
      }
      break;
      
    case SAVE:
      if (override || !save_path_lock)
      {
          FILE* filePtr = fopen(arg, "w+");
          if (filePtr)
          {
              char* path = new char[strlen(arg) + 1];
              if (path)
              {
                  strcpy(path, arg);
                  if (save_path) delete save_path;
                  save_path = path;
                  save_path_lock = override;
              }
              else
              {
                  DMSG(0, "Mgen::OnCommand() Error: memory allocation error: %s\n",
                       GetErrorString());
                  return false;
              }
          }
          else
          {
              DMSG(0, "Mgen::OnCommand() Error: save file open error: %s\n",
                   GetErrorString());
              return false;
          }
      }
      break;
      
    case DEBUG:
      SetDebugLevel(atoi(arg));
      break;
      
    case OFFSET:
      if (override || !offset_lock)
      {
          double timeOffset;
          if (1 == sscanf(arg, "%lf", &timeOffset))
          {
              offset = timeOffset;
              offset_lock = override;
          }
          else
          {
              DMSG(0, "Mgen::OnCommand() Error invalid OFFSET\n");
              return false;
          }
      }
      break;
	  
    case TXLOG:
      log_tx = true;
      break;

    case REUSE:
      if (!arg)
      {
          DMSG(0, "Mgen::OnCommand() Error: missing argument to REUSE\n");
          return false;   
      }
      {
          bool reuseTemp;
          // convert to upper case for case-insensitivity
          char temp[4];
          unsigned int len = strlen(arg);
          len = len < 4 ? len : 4;
          unsigned int i;
          for (i = 0 ; i < len; i++)
            temp[i] = toupper(arg[i]);
          temp[i] = '\0';
          if(!strncmp("ON", temp, len))
              reuseTemp = true;
          else if(!strncmp("OFF", temp, len))
              reuseTemp = false;
          else
          {
              DMSG(0, "Mgen::OnCommand() Error: wrong argument to REUSE: %s\n", arg);
              return false;   
          }
          SetDefaultReuse(reuseTemp);
          break;
      }
      break;
      
    case LOCALTIME:
	  local_time = true;
	  break;
      
    case BINARY:
      if (log_open)
      {
          DMSG(0, "Mgen::OnCommand() Error: BINARY option must precede OUTPUT and LOG commands\n");
          return false;
      }
      log_binary = true;
      break;
      
    case FLUSH:
      log_flush = true;
      break;
      
    case TXCHECKSUM:
      checksum_enable = true;
      break;
      
    case RXCHECKSUM:
      checksum_force = true;
      break;
      
    case CHECKSUM:
      checksum_enable = true;
      checksum_force = true;
      break;

    case QUEUE:	
      int tmpQueueLimit;
      if (1 != sscanf(arg, "%d", &tmpQueueLimit))
      {
          DMSG(0, "Mgen::OnCommand() - invalid queue limit.\n");
          return false;
      }
      SetDefaultQueueLimit(tmpQueueLimit,override);
      break;            
 
    case INVALID_COMMAND:
      DMSG(0, "Mgen::OnCommand() Error: invalid command\n");
      return false;   
    }  // end switch(cmd)
    return true; 
}  // end Mgen::OnCommand()


////////////////////////////////////////////////////////////////
// DrecGroupList implementation

DrecGroupList::DrecGroupList()
 : head(NULL), tail(NULL)
{
    
}

DrecGroupList::~DrecGroupList()
{
    
}
void DrecGroupList::Destroy(Mgen& mgen)
{
    DrecMgenTransport* next = head;
    while (next)
    {
        DrecMgenTransport* current = next;
        next = next->next;
        if (NULL != current->flow_transport)
          mgen.LeaveGroup(current->flow_transport, 
                                    current->group_addr, 
			  current->source_addr,
                                    current->GetInterface());
        
        delete current;
    }
    head = tail = (DrecMgenTransport*)NULL;
}  // end DrecGroupList::Destroy()

bool DrecGroupList::JoinGroup(Mgen&                 mgen,
                              const ProtoAddress&   groupAddress, 
                              const ProtoAddress&   sourceAddress,
                              const char*           interfaceName,
                              UINT16        thePort,
                              bool                  deferred)
{
  DrecMgenTransport* transport = FindMgenTransportByGroup(groupAddress, sourceAddress, interfaceName, thePort);
    if (transport && transport->IsActive())
    {
        DMSG(0, "DrecGroupList::JoinGroup() Error: group already joined\n");
        return false;
    }
    else
    {
        if (!transport)
        {
	  if (!(transport = new DrecMgenTransport(groupAddress, sourceAddress, interfaceName, thePort)))
            { 
                DMSG(0, "DrecGroupList::JoinGroup() Error: DrecGroupList::DrecMgenTransport memory allocation error: %s\n",
                     GetErrorString());
                return false;  
            }
            Append(transport);
        }
        if (deferred)
          return true;
        else
          return transport->Activate(mgen);
    }
}  // end DrecGroupList::JoinGroup()

bool DrecGroupList::LeaveGroup(Mgen&                 mgen,
                               const ProtoAddress&   groupAddress, 
			       const ProtoAddress&   sourceAddress,
                               const char*           interfaceName,
                               UINT16        thePort)
{
  DrecMgenTransport* transport = FindMgenTransportByGroup(groupAddress, sourceAddress, interfaceName, thePort);
    if (transport)
    {
        if (transport->IsActive())
        {
            if (!mgen.LeaveGroup(transport->flow_transport, 
				 groupAddress,
				 sourceAddress,
				 interfaceName))
            {
                DMSG(0, "DrecGroupList::LeaveGroup() Error: group leave error\n");
                return false;
            }
            
        }
        Remove(transport);
        delete transport;
        return true;
    }
    else
    {
        DMSG(0, "DrecGroupList::LeaveGroup() Error: group transport not found\n");
        return false;   
    }
}  // end DrecGroupList::LeaveGroup()

bool DrecGroupList::JoinDeferredGroups(Mgen& mgen)
{
    bool result = true;
    DrecMgenTransport* next = head;
    while (next)
    {
        if (!next->IsActive()) 
            result &= next->Activate(mgen);
        next = next->next;
    }
    return result;
}  // end DrecGroupList::JoinDeferredGroups()


DrecGroupList::DrecMgenTransport* DrecGroupList::FindMgenTransportByGroup(
    const ProtoAddress& groupAddr,
    const ProtoAddress& sourceAddr,
    const char*           interfaceName,
    UINT16        thePort)
{
    DrecMgenTransport* next = head;
    while (next)
    {
        const char* nextInterface = next->GetInterface();
        bool interfaceIsEqual =  (NULL == interfaceName) ? 
                (NULL == nextInterface) : 
                ((NULL != nextInterface) && 
                 !strcmp(nextInterface, interfaceName));
        bool groupIsEqual = next->group_addr.IsValid() ?
            next->group_addr.HostIsEqual(groupAddr) : true;

        bool sourceIsEqual = !next->source_addr.IsValid() ?
                             !sourceAddr.IsValid() :
                             sourceAddr.IsValid() && next->source_addr.HostIsEqual(sourceAddr);        bool portIsEqual = thePort == next->GetPort();
        if (interfaceIsEqual && groupIsEqual && sourceIsEqual && portIsEqual) 
            return next;
        next = next->next;
    }
    return (DrecMgenTransport*)NULL;
}  // end DrecGroupList::FindMgenTransportByGroup()

void DrecGroupList::Append(DrecMgenTransport* transport)
{
    transport->next = NULL;
    if ((transport->prev = tail))
        transport->prev->next = transport;
    else
        head = transport;
    tail = transport;
}  // end DrecGroupList::Append()

void DrecGroupList::Remove(DrecMgenTransport* transport)
{
    if (transport->prev)
        transport->prev->next = transport->next;
    else
        head = transport->next;
    if (transport->next)
        transport->next->prev = transport->prev;
    else
        tail = transport->prev;
}  // end DrecGroupList::Remove()
        
//////////////////////////////////////////////////
// DrecGroupList::DrecMgenTransport implementation

DrecGroupList::DrecMgenTransport::DrecMgenTransport(const ProtoAddress&  groupAddr, 
						    const ProtoAddress& sourceAddr,
                          const char*            interfaceName,
                          UINT16         thePort)
  : flow_transport(NULL), group_addr(groupAddr), source_addr(sourceAddr), port(thePort),
   prev(NULL), next(NULL)
{
    if (interfaceName)
        strncpy(interface_name, interfaceName, 16);
    else
        interface_name[0] = '\0';
} 

DrecGroupList::DrecMgenTransport::~DrecMgenTransport()
{
    
}

bool DrecGroupList::DrecMgenTransport::Activate(Mgen& mgen)
{
    const char* iface = GetInterface();
    flow_transport = mgen.JoinGroup(group_addr, source_addr, iface, port);
    return IsActive();
}


////////////////////////////////////////////////////////////////
// Mgen::FastReader implementation

Mgen::FastReader::FastReader()
    : savecount(0)
{
    
}

Mgen::FastReader::Result Mgen::FastReader::Read(FILE*           filePtr, 
                                                char*           buffer, 
                                                unsigned int*   len)
{
    unsigned int want = *len;   
    if (savecount)
    {
        unsigned int ncopy = want < savecount ? want : savecount;
        memcpy(buffer, saveptr, ncopy);
        savecount -= ncopy;
        saveptr += ncopy;
        buffer += ncopy;
        want -= ncopy;
    }
    while (want)
    {
        unsigned int result = fread(savebuf, sizeof(char), BUFSIZE, filePtr);
        if (result)
        {
            unsigned int ncopy= want < result ? want : result;
            memcpy(buffer, savebuf, ncopy);
            savecount = result - ncopy;
            saveptr = savebuf + ncopy;
            buffer += ncopy;
            want -= ncopy;
        }
        else  // end-of-file
        {
#ifndef _WIN32_WCE
            if (ferror(filePtr))
            {
                if (EINTR == errno) continue;   
            }
#endif // !_WIN32_WCE
            *len -= want;
            if (*len)
                return OK;  // we read at least something
            else
                return DONE; // we read nothing
        }
    }  // end while(want)
    return OK;
}  // end Mgen::FastReader::Read()

/**
 * An OK text readline() routine (reads what will fit into buffer incl. NULL termination)
 * if *len is unchanged on return, it means the line is bigger than the buffer and 
 * requires multiple reads
 */

Mgen::FastReader::Result Mgen::FastReader::Readline(FILE*         filePtr, 
                                                    char*         buffer, 
                                                    unsigned int* len)
{   
    unsigned int count = 0;
    unsigned int length = *len;
    char* ptr = buffer;
    while (count < length)
    {
        unsigned int one = 1;
        switch (Read(filePtr, ptr, &one))
        {
            case OK:
                if (('\n' == *ptr) || ('\r' == *ptr))
                {
                    *ptr = '\0';
                    *len = count;
                    return OK;
                }
                count++;
                ptr++;
                break;
                
            case ERROR_:
                return ERROR_;
                
            case DONE:
                return DONE;
        }
    }
    // We've filled up the buffer provided with no end-of-line 
    return ERROR_;
}  // end Mgen::FastReader::Readline()

/**
 * This reads a line with possible ending '\' continuation character.
 * Such "continued" lines are returned as one line with this function
 *  and the "lineCount" argument indicates the actual number of lines
 *  which comprise the long "continued" line.
 *
 * @note Lines ending with an even number '\\' are considered ending 
 *        with one less '\' instead of continuing.  So, to actually 
 *        end a line with an even number of '\\', continue it with 
 *        an extra '\' and follow it with a blank line.)
 */
Mgen::FastReader::Result Mgen::FastReader::ReadlineContinue(FILE*         filePtr, 
                                                            char*         buffer, 
                                                            unsigned int* len,
                                                            unsigned int* lineCount)
{   
    unsigned int lines = 0;
    unsigned int count = 0;
    unsigned int length = *len;
    char* ptr = buffer;
    while (count < length)
    {
        unsigned int space = length - count;
        switch (Readline(filePtr, ptr, &space))
        {
            case OK:
            {
                lines++;
                // 1) Does the line continue?
                char* cptr = space ? ptr + space - 1 : ptr;
                // a) skip trailing white space
                while (((' ' == *cptr) ||  ('\t' == *cptr)) && (cptr > ptr)) 
                {
                    space--;
                    cptr--;
                }
                
                // If line "continues" to a blank line, skip it
                if ((cptr == ptr) && ((*cptr == '\0') || isspace(*cptr))) 
                    continue;
                
                if ('\\' == *cptr)
                {
                    // Make sure line ends with odd number of '\' to continue
                    bool lineContinues = false;
                    while ((cptr >= ptr) && ('\\' == *cptr))
                    {
                        cptr--;
                        lineContinues = lineContinues ? false : true;    
                    }  
                    // lose trailing '\' (continuation or extra '\' char)
                    *(ptr+space-1) = '\0';
                    space -= 1;
                    if (lineContinues)
                    {
                        // get next line to continue
                        count += space;
                        ptr += space;
                        continue;
                    }
                }
                *len = count + space;
                if (lineCount) *lineCount = lines;
                return OK;  
                break;
            }
                
            case ERROR_:
                return ERROR_;
                
            case DONE:
                return DONE;
        }
    }
    // We've filled up the buffer provided with no end-of-line 
    return ERROR_;
}  // end Mgen::FastReader::Readline()
