/******************************************************************************
 * Copyright (c) 2000-2025 Ericsson Telecom AB
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
 *
 * Contributors:
 *   Baji, Laszlo
 *   Balasko, Jeno
 *   Czimbalmos, Eduard
 *   Feher, Csaba
 *   Kovacs, Ferenc
 *   Pilisi, Gergely
 *   Raduly, Csaba
 *   Szabados, Kristof
 *   Szabo, Janos Zoltan – initial implementation
 *   Szalai, Gabor
 *   Pandi, Krisztian
 *
 ******************************************************************************/
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <math.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include <sys/select.h>
#include <sys/types.h>
#include <poll.h>


#ifdef USE_EPOLL
#include <sys/epoll.h>
#endif

#include "Types.h"

#include "Snapshot.hh"
#include "Fd_And_Timeout_User.hh"
#include "Timer.hh"
#include "Logger.hh"
#include "Error.hh"
#include "Event_Handler.hh"


static const int MAX_INT_VAL = static_cast<int>(static_cast<unsigned int>( -2 ) >> 1u);

/******************************************************************************
 * class FdMap                                                                *
 ******************************************************************************/

int             FdMap::nItems;
int             FdMap::capacity;

my_unordered_type<int,FdMap::Data> FdMap::items;

#ifndef USE_EPOLL
std::vector<pollfd> FdMap::pollFds;
boolean            FdMap::needUpdate;
int             FdMap::nPollFdsFrozen;
#endif
#ifdef USE_EPOLL
int             FdMap::epollFd;
epoll_event     FdMap::epollEvents[MAX_EPOLL_EVENTS];
#endif

fd_event_type_enum FdMap::add(int fd, Fd_Event_Handler * handler,
  fd_event_type_enum event)
{
  if (handler == 0)
    TTCN_error("FdMap::add: Internal error"); // debug

  if (fd < 0 || fd >= capacity) {
    TTCN_error_begin("Trying to add events of an invalid file descriptor "
      "(%d) to the set of events handled by \"", fd);
    handler->log();
    TTCN_Logger::log_event("\".");
    TTCN_error_end();
  }

  if ((event & ~(FD_EVENT_RD | FD_EVENT_WR | FD_EVENT_ERR)) != 0) {
    TTCN_error_begin("Trying to add invalid events (%d) of file descriptor "
      "(%d) to the set of events handled by \"", event, fd);
    handler->log();
    TTCN_Logger::log_event("\".");
    TTCN_error_end();
  }

#ifndef USE_EPOLL
  short pollEvent = eventToPollEvent(event);
#endif
  if (findInItems(fd)) {
    // Already exists...
    if (items[fd].hnd != 0 && items[fd].hnd != handler) {
      TTCN_error_begin("Trying to add file descriptor (%d) "
        "events (%d) to the set of events handled by \"",fd,event);
      handler->log();
      TTCN_Logger::log_event("\", but the events of the "
        "file descriptor already have a different handler: \"");
      if (items[fd].hnd != 0) items[fd].hnd->log();
      TTCN_Logger::log_event("\".");
      TTCN_error_end();
    }
    fd_event_type_enum oldEvent;
#ifdef USE_EPOLL
    oldEvent = static_cast<fd_event_type_enum>(items[fd].evt);
    items[fd].evt |= static_cast<short>(event);
#else
    items[fd].hnd = handler; // in case it is a frozen deleted item
    oldEvent = pollEventToEvent(pollFds[items[fd].ixPoll].events);
    pollFds[items[fd].ixPoll].events |= pollEvent;
#endif
    return oldEvent;
  }
  // adding item...
#ifdef USE_EPOLL
  items[fd].evt = static_cast<short>(event);
  items[fd].ixE = -1;
#else
  pollfd new_item;
  new_item.fd = fd;
  new_item.events = pollEvent;
  new_item.revents = 0;
  pollFds.push_back(new_item);
  items[fd].ixPoll = nItems;
#endif
  items[fd].hnd = handler;
  ++nItems;
  return static_cast<fd_event_type_enum>(0);
}

fd_event_type_enum FdMap::remove(int fd, const Fd_Event_Handler * handler,
  fd_event_type_enum event)
{
  // Errors in Test Ports may be detected at this point
  // handler is used only for checking;
  // handler is 0 to remove a frozen deleted item
  if (fd < 0 || fd >= capacity) {
    TTCN_error_begin("Trying to remove events of an invalid file "
      "descriptor (%d) from the set of events handled by \"", fd);
    if (handler != 0) handler->log();
    TTCN_Logger::log_event("\".");
    TTCN_error_end();
  }
  if ((event & ~(FD_EVENT_RD | FD_EVENT_WR | FD_EVENT_ERR)) != 0) {
    TTCN_error_begin("Trying to remove invalid events (%d) of file "
      "descriptor (%d) from the set of events handled by \"", event, fd);
    if (handler != 0) handler->log();
    TTCN_Logger::log_event("\".");
    TTCN_error_end();
  }
  fd_event_type_enum oldEvent;
#ifndef USE_EPOLL
  short pollEvent = eventToPollEvent(event);
#endif
  if (!findInItems(fd)) {
    TTCN_error_begin("Trying to remove file descriptor (%d) "
      "events (%d) from the set of events handled by \"",
      fd, event);
    if (handler != 0) handler->log();
    TTCN_Logger::log_event("\", but events of the file descriptor "
      "do not have a handler.");
    TTCN_error_end();
  }
  if (items[fd].hnd != handler) {
    TTCN_error_begin("Trying to remove file descriptor (%d) "
      "events (%d) from the set of events handled by \"", fd, event);
    if (handler != 0) handler->log();
    TTCN_Logger::log_event("\", but the events of the "
      "file descriptor have different handler: \"");
    items[fd].hnd->log(); TTCN_Logger::log_event("\".");
    TTCN_error_end();
  }
#ifdef USE_EPOLL
  short ixE = items[fd].ixE;
  // If there is an unprocessed active event on this fd, clear it
  if (ixE >= 0) epollEvents[ixE].events &= ~eventToEpollEvent(event);
  oldEvent = static_cast<fd_event_type_enum>(items[fd].evt);
  if ((items[fd].evt &= ~static_cast<short>(event)) == 0) {
    --nItems;
    items.erase(fd);
  }
#else
  int i = items[fd].ixPoll;
  oldEvent = pollEventToEvent(pollFds[i].events);
  pollFds[i].revents &= ~pollEvent;
  if ((pollFds[i].events &= ~pollEvent) == 0) {
    if (i >= nPollFdsFrozen) {
      --nItems;
      if (i < nItems) {
        pollFds[i] = pollFds[nItems];
        items[pollFds[i].fd].ixPoll = i;
      }
      pollFds.pop_back();
      items.erase(fd);
    } else { // The item is frozen; removal is postponed.
      items[fd].hnd = 0;
      needUpdate = TRUE;
    }
  }
#endif
  
  return oldEvent;
}

fd_event_type_enum FdMap::find(int fd, Fd_Event_Handler * * handler)
{
  Data  * data;
  if (!findInItems(fd)) {
    *handler = 0; return static_cast<fd_event_type_enum>(0);
  }
  data = &items[fd];
  *handler = data->hnd;
#ifdef USE_EPOLL
  return static_cast<fd_event_type_enum>(data->evt);
#else
  return pollEventToEvent(pollFds[data->ixPoll].events);
#endif
}


#ifdef USE_EPOLL
void FdMap::epollMarkFds(int nEvents)
{
  for (int i = 0; i < nEvents; ++i) {
    int fd = epollEvents[i].data.fd;
    if (findInItems(fd)) items[fd].ixE = i;
  }
}

void FdMap::epollUnmarkFds(int nEvents)
{
  for (int i = 0; i < nEvents; ++i) {
    int fd = epollEvents[i].data.fd;
    if (findInItems(fd)) items[fd].ixE = -1;
  }
}
#else
void FdMap::pollFreeze()
{
  nPollFdsFrozen = nItems;
}

void FdMap::pollUnfreeze()
{
  if (!needUpdate) { nPollFdsFrozen = 0; return; }
  int i = 0, j = nPollFdsFrozen;
  nPollFdsFrozen = 0;
  while (i < j) {
    pollfd  & pollFd = pollFds[i];
    if (pollFd.events == 0) {
      remove(pollFd.fd, 0, static_cast<fd_event_type_enum>(
        FD_EVENT_RD | FD_EVENT_WR | FD_EVENT_ERR));
      if (nItems < j) j = nItems;
      // item at index i is changed and has to be checked if exists
    } else ++i;
  }
  needUpdate = FALSE;
}

fd_event_type_enum FdMap::getPollREvent(int fd)
{
  if (findInItems(fd)) {
      return pollEventToEvent(pollFds[items[fd].ixPoll].revents);
  }
  return static_cast<fd_event_type_enum>(0);
}
#endif





/******************************************************************************
 * class Fd_Event_Handler                                                     *
 ******************************************************************************/

void Fd_Event_Handler::log() const
{
  TTCN_Logger::log_event("handler <invalid>");
}






/******************************************************************************
 * class Fd_And_Timeout_Event_Handler                                         *
 ******************************************************************************/

void Fd_And_Timeout_Event_Handler::Handle_Fd_Event(int,
  boolean, boolean, boolean)
{
  TTCN_error("Fd_And_Timeout_Event_Handler::Handle_Fd_Event: "
    "Erroneous usage of class Fd_And_Timeout_Event_Handler");
  // This method cannot be pure virtual because of necessary instantiation
  // inside TITAN
}

void Fd_And_Timeout_Event_Handler::Handle_Timeout(double)
{
  TTCN_error("Fd_And_Timeout_Event_Handler::Handle_Timeout: "
    "Erroneous usage of class Fd_And_Timeout_Event_Handler");
  // This method cannot be pure virtual because of necessary instantiation
  // inside TITAN
}

void Fd_And_Timeout_Event_Handler::Event_Handler(const fd_set *,
  const fd_set *, const fd_set *, double)
{
  TTCN_error("Fd_And_Timeout_Event_Handler::Event_Handler: "
    "Erroneous usage of class Fd_And_Timeout_Event_Handler");
  // This method cannot be pure virtual because of necessary instantiation
  // inside TITAN
}

Fd_And_Timeout_Event_Handler::~Fd_And_Timeout_Event_Handler()
{
  // In case the event handler forgot to stop its timer,
  // stop it at this point.
  Fd_And_Timeout_User::set_timer(this, 0.0);
  // In case the event handler forgot to remove all of its file descriptor
  // events, remove those at this point.
  Fd_And_Timeout_User::remove_all_fds(this);
}

void Fd_And_Timeout_Event_Handler::log() const
{
  TTCN_Logger::log_event("handler <unknown>");
}





/* The maximal blocking time to be used in poll, epoll and select (in seconds).
 * On some systems (e.g. Solaris) the select call is a libc wrapper for
 * poll(2). In the wrapper the overflows are not always handled thus select
 * may return EINVAL in some cases.
 * This value is derived from the maximal possible value for the last argument
 * of poll, which is measured in milliseconds. */
#define MAX_BLOCK_TIME (~(0x80 << ((sizeof(int) - 1) * 8)) / 1000)





/******************************************************************************
 * class Fd_And_Timeout_User                                                  *
 ******************************************************************************/

Handler_List Fd_And_Timeout_User::timedList,
    Fd_And_Timeout_User::oldApiCallList;
FdSets * Fd_And_Timeout_User::fdSetsReceived;
FdSets * Fd_And_Timeout_User::fdSetsToHnds;
int Fd_And_Timeout_User::nOldHandlers;
boolean Fd_And_Timeout_User::is_in_call_handlers;
int Fd_And_Timeout_User::curRcvdEvtIx;

inline void Fd_And_Timeout_User::checkFd(int fd)
{
  if (
#ifdef USE_EPOLL
    fd == FdMap::epollFd ||
#endif
    fcntl(fd, F_GETFD, FD_CLOEXEC) < 0)
    TTCN_error("Trying to add events of an invalid file descriptor (%d)", fd);
}

void Fd_And_Timeout_User::add_fd(int fd, Fd_Event_Handler * handler,
  fd_event_type_enum event)
{
  fd_event_type_enum oldEvent = FdMap::add(fd, handler, event);
  Fd_And_Timeout_Event_Handler * tmHnd =
    dynamic_cast<Fd_And_Timeout_Event_Handler *>(handler);
  if (tmHnd != 0) {
    if (tmHnd->fdSets != 0) {
      if (fd >= (int)FD_SETSIZE)
        TTCN_error("The file descriptor (%d) to be added is too big "
          "to be handled by Event_Handler. FD_SETSIZE is %d",
          fd, FD_SETSIZE);
      tmHnd->fdSets->add(fd, event);
    }
    if (oldEvent == 0) ++tmHnd->fdCount;
  }
#ifdef USE_EPOLL
  epoll_event epollEvent;
  memset(&epollEvent, 0, sizeof(epollEvent));
  epollEvent.events = FdMap::eventToEpollEvent(oldEvent | event);
  epollEvent.data.fd = fd;
  if ( ( (oldEvent == 0) ?
    epoll_ctl(FdMap::epollFd, EPOLL_CTL_ADD, fd, &epollEvent) :
    epoll_ctl(FdMap::epollFd, EPOLL_CTL_MOD, fd, &epollEvent) ) < 0 ) {
    int saved_errno=errno;
    checkFd(fd);
    TTCN_error("Fd_And_Timeout_User::add_fd: System call epoll_ctl failed "
      "when adding fd: %d, errno: %d", fd, saved_errno);
  }
#else
  checkFd(fd);
#endif
}

void Fd_And_Timeout_User::remove_fd(int fd, Fd_Event_Handler * handler,
  fd_event_type_enum event)
{
  if (handler == 0)
    TTCN_error("Fd_And_Timeout_User::remove_fd: Internal error"); // debug
  fd_event_type_enum oldEvent = FdMap::remove(fd, handler, event);
  // Ignore errors for HP53582.
  if (oldEvent == FD_EVENT_ERR) return;
  fd_event_type_enum newEvent =
    static_cast<fd_event_type_enum>(oldEvent & ~event);
  Fd_And_Timeout_Event_Handler * tmHnd =
    dynamic_cast<Fd_And_Timeout_Event_Handler *>(handler);
  if (tmHnd != 0) {
    if (newEvent == 0) --tmHnd->fdCount;
    if (tmHnd->getIsOldApi()) {
      fdSetsReceived->remove(fd, event);
      tmHnd->fdSets->remove(fd, event);
    }
  }
#ifdef USE_EPOLL
  epoll_event epollEvent;
  memset(&epollEvent, 0, sizeof(epollEvent));
  epollEvent.data.fd = fd;
  if (newEvent == 0) {
    if (epoll_ctl(FdMap::epollFd, EPOLL_CTL_DEL, fd, &epollEvent) < 0) {
      // Check if fd exists. If not, assume, that it was closed earlier.
      int errno_tmp = errno;
      if (fcntl(fd, F_GETFD, FD_CLOEXEC) >= 0) {
        errno = errno_tmp;
        TTCN_error("System call epoll_ctl failed when deleting fd: %d, "
          "errno: %d", fd, errno);
      }
      // fd was closed before removing it from the database of TITAN.
      // This removes fd from the epoll set causing epoll_ctl to fail.
      errno = 0; // It is not an error if epoll_ctl fails here
    }
  } else {
    // There is still event type to wait for. - This is the unusual case.
    epollEvent.events = FdMap::eventToEpollEvent(newEvent);
    if (epoll_ctl(FdMap::epollFd, EPOLL_CTL_MOD, fd, &epollEvent) < 0) {
      TTCN_error("System call epoll_ctl failed when removing  fd: %d, "
        "errno: %d", fd, errno);
    }
  }
#endif
}

void Fd_And_Timeout_User::set_timer(Fd_And_Timeout_Event_Handler * handler,
  double call_interval,
  boolean is_timeout, boolean call_anyway, boolean is_periodic)
{
  if (call_interval != 0.0) {
    if (handler->list == 0) timedList.add(handler);
    handler->callInterval = call_interval;
    handler->last_called = TTCN_Snapshot::time_now();
    handler->isTimeout = is_timeout;
    handler->callAnyway = call_anyway;
    handler->isPeriodic = is_periodic;
  } else {
    if (handler->list == &timedList) timedList.remove(handler);
    // Note: Normally the port may be only in timedList or in no list.
    // - The port is put in oldApiCallList only temporarily while calling
    // event handlers.
    // The set_timer method may be called from outside snapshot evaluation
    // or in the event handler. In both cases the port is removed from
    // oldApiCallList beforehand.
    // - However when MC requests a port to be unmapped: the request is
    // processed in the event handler of MC_Connection. In this event
    // handler a port may be unmapped which has been put in oldApiCallList
    // temporarily.
    handler->callInterval = 0.0;
  }
}

void Fd_And_Timeout_User::set_fds_with_fd_sets(
  Fd_And_Timeout_Event_Handler * handler,
  const fd_set *read_fds, const fd_set *write_fds, const fd_set *error_fds)
{
  // Probaly in class PORT: Install_Handler should not be possible to be
  // called from new event handlers
  int fdLimit = FdMap::getFdLimit();
  if ((int)FD_SETSIZE < fdLimit) fdLimit = FD_SETSIZE;
  if (handler->fdSets == 0) {
    if (handler->fdCount != 0) {
      // Usage of the new and old API is mixed. - It should not happen.
      // It is handled, but not most efficiently.
      remove_all_fds(handler);
    }
    handler->fdSets = new FdSets;
    ++nOldHandlers;
    if (fdSetsReceived == 0) fdSetsReceived = new FdSets;
    if (fdSetsToHnds == 0) fdSetsToHnds = new FdSets;
  }
  FdSets * fdSets = handler->fdSets;
  fd_event_type_enum eventOld, eventNew;
#ifdef USE_EPOLL
  // Removing fds which refer to different descriptors than
  // in the previous call. (closed and other fd created with same id)
  epoll_event epollEvent;
  for (int fd = 0; ; ++fd) {
    fd = fdSets->getIxBothAnySet(read_fds, write_fds, error_fds,fd,fdLimit);
    if (fd >= fdLimit) break;
    memset(&epollEvent, 0, sizeof(epollEvent));
    epollEvent.data.fd = fd;
    // Check (inverse) if fd is still in the epoll set
    if (epoll_ctl(FdMap::epollFd, EPOLL_CTL_ADD, fd, &epollEvent) >= 0 ) {
      // fd was not in the epoll set as fd was closed and a new
      // descriptor was created with the same id.
      eventOld = fdSets->getEvent(fd);
      Fd_And_Timeout_User::remove_fd(fd, handler, eventOld);
    } else {
      errno = 0; // fd is already in the epoll set - it is not an error
    }
  }
#endif
  fd_event_type_enum event;
  for (int fd = 0; ; ++fd) {
    fd = fdSets->getIxDiff(read_fds, write_fds, error_fds, fd, fdLimit);
    if (fd >= fdLimit) break;
    eventOld = fdSets->getEvent(fd);
    eventNew = FdSets::getEvent(read_fds, write_fds, error_fds, fd);
    event = static_cast<fd_event_type_enum>(eventNew & ~eventOld);
    if (event != 0) Fd_And_Timeout_User::add_fd(fd, handler, event);
    event = static_cast<fd_event_type_enum>(eventOld & ~eventNew);
    if (event != 0) Fd_And_Timeout_User::remove_fd(fd, handler, event);
  }
}

void Fd_And_Timeout_User::remove_all_fds(Fd_And_Timeout_Event_Handler * handler)
{
  my_unordered_type<int,FdMap::Data>::iterator fdmap_iter=FdMap::items.begin();
  while (handler->fdCount != 0 && fdmap_iter!=FdMap::items.end()) {
    int fd=fdmap_iter->first;
    Fd_Event_Handler * hnd = fdmap_iter->second.hnd;
    fdmap_iter++; // advance the iterator before remove this element
    if(hnd==handler){
      Fd_And_Timeout_User::remove_fd(fd, handler, static_cast<fd_event_type_enum>(FD_EVENT_RD | FD_EVENT_WR | FD_EVENT_ERR));
    }
  }
  
  
  if (handler->fdSets != 0) {
    delete handler->fdSets; handler->fdSets = 0;
    --nOldHandlers;
    if (nOldHandlers == 0) {
      delete fdSetsReceived; fdSetsReceived = 0;
      delete fdSetsToHnds; fdSetsToHnds = 0;
    }
  }
}

boolean Fd_And_Timeout_User::getTimeout(double * timeout)
{
  timedList.first();
  if (timedList.finished()) return FALSE;

  Fd_And_Timeout_Event_Handler * handler = timedList.current();
  double earliestTimeout = handler->last_called + handler->callInterval;
  timedList.next();

  while (!timedList.finished()) {
    handler = timedList.current();
    timedList.next();
    double nextCall = handler->last_called + handler->callInterval;
    if (nextCall < earliestTimeout) earliestTimeout = nextCall;
  }
  *timeout = earliestTimeout;
  return TRUE;
}

void Fd_And_Timeout_User::call_handlers(int nEvents)
{
  try { // To keep consistency in case of exceptions
    is_in_call_handlers = TRUE;
    if (nOldHandlers != 0) { fdSetsReceived->clear(); }
    if (nEvents > 0) {
      // Note: FdMap may be modified during event handler calls
#ifdef USE_EPOLL
      FdMap::epollMarkFds(nEvents);
      int ixLimit = nEvents;
#else
      FdMap::pollFreeze();
      int ixLimit = FdMap::getSize();
      // Below this index pollfd array items are not removed
      // If an item should have been removed, then the events field is 0
#endif
      try { // To keep consistency in case of exceptions
        for (int ix = 0; ix != ixLimit; ++ix) {
#ifdef USE_EPOLL
          int fd = FdMap::epollEvents[ix].data.fd;
          fd_event_type_enum  event =
            FdMap::epollEventToEvent(FdMap::epollEvents[ix].events);
#else
          pollfd * pollFd = &(FdMap::getPollFds()[ix]);
          if ((pollFd->revents & FdMap::pollEventMask) == 0) continue;
          int fd = pollFd->fd;
          fd_event_type_enum event = FdMap::pollEventToEvent(pollFd->revents);
          // The event handler may need pollFd.revents or epoll .events
#endif
          Fd_Event_Handler    * handler = 0;
          fd_event_type_enum wEvent = FdMap::find(fd, &handler);
          if (wEvent != 0) {
            event = static_cast<fd_event_type_enum>(
              event & (wEvent | FD_EVENT_ERR));
            if (event != 0) {
              curRcvdEvtIx = ix; // see getCurReceivedEvent()
              Fd_And_Timeout_Event_Handler * tmHnd =
                dynamic_cast<Fd_And_Timeout_Event_Handler*>(handler);
              if (tmHnd != 0 && tmHnd->getIsOldApi()) {
                fdSetsReceived->add(fd, event);
                if (tmHnd->list == 0) oldApiCallList.add(tmHnd);
              } else
                handler->Handle_Fd_Event(fd, (event & FD_EVENT_RD) != 0,
                  (event & FD_EVENT_WR) != 0,
                  (event & FD_EVENT_ERR) != 0);
              if (tmHnd != 0 && tmHnd->list == &timedList)
                tmHnd->hasEvent = TRUE;
            }
          }
#ifndef USE_EPOLL
          pollFd->revents = 0;
#endif
        }
      } catch(...) {
#ifdef USE_EPOLL
        FdMap::epollUnmarkFds(nEvents);
#else
        FdMap::pollUnfreeze();
#endif
        throw;
      }
#ifdef USE_EPOLL
      FdMap::epollUnmarkFds(nEvents);
#else
      FdMap::pollUnfreeze();
#endif
      // Call handlers with old API without timer
      for (oldApiCallList.first(); !oldApiCallList.finished(); ) {
        Fd_And_Timeout_Event_Handler * handler = oldApiCallList.current();
        oldApiCallList.next();
        oldApiCallList.remove(handler);
        // Check if the event handler was uninstalled in the meanwhile
        // (Currently the check is superfluous as an other event handler
        //  may not uninstall this event handler.)
        if (handler->fdSets == 0) continue;
        // Get the common set of received and waited events
        // Check if the set contains any element
        if ( fdSetsToHnds->setAnd(*fdSetsReceived, *handler->fdSets) ) {
          double current_time = TTCN_Snapshot::time_now();
          double time_since_last_call = current_time -
            handler->last_called;
          handler->last_called = current_time;
          handler->Event_Handler(fdSetsToHnds->getReadFds(),
            fdSetsToHnds->getWriteFds(), fdSetsToHnds->getErrorFds(),
            time_since_last_call);
        }
      }
    }
    // Call timeout handlers (also handlers with old API with timer)
    double current_time = TTCN_Snapshot::time_now();
    for  (timedList.first(); !timedList.finished(); ) {
      Fd_And_Timeout_Event_Handler * handler = timedList.current();
      timedList.next(); // The handler may be removed from the list
      if (handler->getIsOldApi())
        handler->hasEvent =
          fdSetsToHnds->setAnd(*fdSetsReceived, *handler->fdSets);
      boolean callHandler = (handler->hasEvent && handler->isTimeout) ?
        handler->callAnyway :
      current_time > (handler->last_called + handler->callInterval);
      if ( !handler->isPeriodic &&
        (callHandler || (handler->hasEvent && handler->isTimeout)) ) {
        handler->callInterval = 0.0;
        timedList.remove(handler);
      }
      handler->hasEvent = FALSE;
      if (callHandler) {
        double time_since_last_call = current_time - handler->last_called;
        handler->last_called = current_time;
        if (!handler->getIsOldApi())
          handler->Handle_Timeout(time_since_last_call);
        else if (handler->fdSets != 0)
          handler->Event_Handler(fdSetsToHnds->getReadFds(),
            fdSetsToHnds->getWriteFds(), fdSetsToHnds->getErrorFds(),
            time_since_last_call);
        current_time = TTCN_Snapshot::time_now();
      }
    }
    is_in_call_handlers = FALSE;
  } catch (...) { oldApiCallList.clear(); is_in_call_handlers = FALSE; throw; }
}

int Fd_And_Timeout_User::receiveEvents(int pollTimeout)
{
  int ret_val;
#ifdef USE_EPOLL
  ret_val = epoll_wait(FdMap::epollFd,
    FdMap::epollEvents, FdMap::MAX_EPOLL_EVENTS, pollTimeout);
  if (ret_val < 0 && errno != EINTR)
    TTCN_error("System call epoll_wait() failed when taking a new snapshot.");
#else
  ret_val = poll(FdMap::getPollFds(), FdMap::getSize(), pollTimeout);
  if (ret_val < 0 && errno != EINTR)
    TTCN_error("System call poll() failed when taking a new snapshot.");
#endif
  return ret_val;
}

#ifdef USE_EPOLL
void Fd_And_Timeout_User::reopenEpollFd()
{
  if (FdMap::epollFd != -1) { close (FdMap::epollFd); FdMap::epollFd = -1; }
  FdMap::epollFd = epoll_create(16 /* epoll size hint */);
  // FIXME: method to determine the optimal epoll size hint
  // epoll size hint is ignored in newer kernels
  // for older kernels: it should be big enough not to be too slow
  //                    and it should not be very big to limit memory usage
  if (FdMap::epollFd < 0)
    TTCN_error("System call epoll_create() failed in child process.");

  if (FdMap::getSize() != 1)
    TTCN_error("Fd_And_Timeout_User::reopenEpollFd: Internal error");
}
#endif





/******************************************************************************
 * class TTCN_Snapshot                                                        *
 ******************************************************************************/

boolean TTCN_Snapshot::else_branch_found;
double TTCN_Snapshot::alt_begin;

void TTCN_Snapshot::initialize()
{
  long openMax = sysconf(_SC_OPEN_MAX);
  int fdLimit = (openMax <= (long) MAX_INT_VAL) ? (int) openMax : MAX_INT_VAL;

  FdMap::initialize(fdLimit);
  Fd_And_Timeout_User::initialize();
#ifdef USE_EPOLL
  FdMap::epollFd = epoll_create(16 /* epoll size hint */);
  // FIXME: method to determine the optimal epoll size hint
  // epoll size hint is ignored in newer kernels
  // for older kernels: it should be big enough not to be too slow
  //                    and it should not be very big to limit memory usage
  if (FdMap::epollFd < 0)
    TTCN_error("TTCN_Snapshot::initialize: "
      "System call epoll_create() failed.");
#endif
  else_branch_found = FALSE;
  alt_begin = time_now();
}

void TTCN_Snapshot::check_fd_setsize()
{
  if ((long) FdMap::getFdLimit() > (long) FD_SETSIZE)
    TTCN_Logger::log_fd_limits(FdMap::getFdLimit(), (long) FD_SETSIZE);
}

void TTCN_Snapshot::terminate()
{
#ifdef USE_EPOLL
  if (FdMap::epollFd != -1) { close (FdMap::epollFd); FdMap::epollFd = -1; }
#endif
  Fd_And_Timeout_User::terminate();
}

void TTCN_Snapshot::else_branch_reached()
{
  if (!else_branch_found) {
    else_branch_found = TRUE;
    TTCN_warning("An [else] branch of an alt construct has been reached. "
      "Re-configuring the snapshot manager to call the event handlers "
      "even when taking the first snapshot.");
  }
}

double TTCN_Snapshot::time_now()
{
  static time_t start_time;
  static boolean first_call = TRUE;
  struct timeval tv;
  if (gettimeofday(&tv, NULL) == -1)
    TTCN_error("gettimeofday() system call failed.");
  if (first_call) {
    start_time = tv.tv_sec;
    first_call = FALSE;
  }
  return (double)(tv.tv_sec - start_time) + 1e-6 * (double)tv.tv_usec;
}


void TTCN_Snapshot::take_new(boolean block_execution)
{
  if (block_execution || else_branch_found) {

    // jump here if epoll()/poll()/select() was interrupted by a signal
    again:
    // determine the timeout value for epoll()/poll()/select()
    double timeout = 0.0;
    int pollTimeout = 0; // timeout for poll/epoll
    boolean handleTimer = FALSE;
    if (block_execution) {
      // find the earliest timeout
      double timer_timeout, handler_timeout = 0.0;
      boolean is_timer_timeout = TIMER::get_min_expiration(timer_timeout);
      boolean is_handler_timeout =
        Fd_And_Timeout_User::getTimeout(&handler_timeout);
      if (is_timer_timeout) {
        if (is_handler_timeout && handler_timeout < timer_timeout)
          timeout = handler_timeout;
        else timeout = timer_timeout;
      } else if (is_handler_timeout) timeout = handler_timeout;
      if (is_timer_timeout || is_handler_timeout) {
        // there are active TTCN-3 or test port timers
        double current_time = time_now();
        double block_time = timeout - current_time;
        if (block_time > 0.0) {
          // the first timeout is in the future: blocking is needed
          // filling up tv with appropriate values
          if (block_time < (double)MAX_BLOCK_TIME) {
            pollTimeout = static_cast<int>(floor(block_time*1000));
            handleTimer = TRUE;
          } else {
            pollTimeout = MAX_BLOCK_TIME * 1000;
            handleTimer = TRUE;
          }
        } else {
          // first timer is already expired: do not block
          // pollTimeout is 0
          handleTimer = TRUE;
        }
      } else {
        // no active timers: infinite timeout
        pollTimeout = -1;
      }
    } else {
      // blocking was not requested (we are in a first snapshot after an
      // [else] branch): do not block - pollTimeout is 0
    }

    if (FdMap::getSize() == 0 && pollTimeout < 0)
      TTCN_error("There are no active timers and no installed event "
        "handlers. Execution would block forever.");

    int ret_val = 0;
    if (FdMap::getSize() != 0) {
      ret_val = Fd_And_Timeout_User::receiveEvents(pollTimeout);
      // call epoll_wait() / poll()
      if (ret_val < 0) { /* EINTR - signal */ errno = 0; goto again; }
    } else {
      // There isn't any file descriptor to check
      if (pollTimeout > 0) {
        // waiting only for a timeout in the future
        timeval tv;
        tv.tv_sec = pollTimeout / 1000;
        tv.tv_usec = (pollTimeout % 1000) * 1000;
        ret_val = select(0, NULL, NULL, NULL, &tv);
        if (ret_val < 0 && errno == EINTR) {
          /* signal */ errno = 0; goto again;
        }
        if ((ret_val < 0 && errno != EINTR) || ret_val > 0)
          TTCN_error("System call select() failed when taking a new "
            "snapshot.");
      } else if (pollTimeout < 0)
        TTCN_error("There are no active timers or installed event "
          "handlers. Execution would block forever.");
    }

    if (ret_val > 0) {
      Fd_And_Timeout_User::call_handlers(ret_val);
    } else if (ret_val == 0 && handleTimer) {
      // if select() returned because of the timeout, but too early
      // do an other round if it has to wait much,
      // or do a busy wait if only a few cycles are needed
      if (pollTimeout > 0){
        double difference = time_now() - timeout;
        if(difference < 0.0){
          if(difference < -0.001){
            errno = 0;
            goto again;
          } else {
            while (time_now() < timeout) ;
          }
        }
      }
      Fd_And_Timeout_User::call_handlers(0); // Call timeout handlers
    }
  }
  // just update the time and check the testcase guard timer if blocking was
  // not requested and there is no [else] branch in the test suite

  alt_begin = time_now();

  if (testcase_timer.timeout() == ALT_YES)
    TTCN_error("Guard timer has expired. Execution of current test case "
      "will be interrupted.");
}

void TTCN_Snapshot::block_for_sending(int send_fd, Fd_Event_Handler * handler)
{
  // To be backward compatible: handler is optional
  if (Fd_And_Timeout_User::get_is_in_call_handlers())
    TTCN_error("TTCN_Snapshot::block_for_sending: The function may not be "
      "called from event handler");
  Fd_Event_Handler * hnd = 0;
  if ((FdMap::find(send_fd, &hnd) & FD_EVENT_WR) != 0)
    TTCN_error("TTCN_Snapshot::block_for_sending: An event handler already "
      "waits for file descriptor %d to be writable", send_fd);
  if (handler != 0 && hnd != 0 && handler != hnd)
    TTCN_error("TTCN_Snapshot::block_for_sending: File descriptor %d "
      "already has a handler, which is different from the currently "
      "specified.", send_fd);
  static Fd_And_Timeout_Event_Handler dummyHandler;
  if (hnd == 0) { hnd = (handler != 0) ? handler : &dummyHandler; }
  Fd_And_Timeout_User::add_fd(send_fd, hnd, FD_EVENT_WR);
  for ( ; ; ) {
    int ret_val = Fd_And_Timeout_User::receiveEvents(-1); // epoll / poll
    if (ret_val >= 0) {
    boolean writable = FALSE;
    boolean readable  = FALSE;
#ifdef USE_EPOLL

      for (int i = 0; i < ret_val; ++i) {
        if (FdMap::epollEvents[i].data.fd == send_fd) {
          readable = TRUE;
          if ((FdMap::epollEvents[i].events & EPOLLOUT) != 0){
            writable = TRUE;
          }
          break;
        }
      }
#else
      if (FdMap::getPollREvent(send_fd) != 0) {readable = TRUE;}
      if ((FdMap::getPollREvent(send_fd) & FD_EVENT_WR) != 0) {writable = TRUE;}
#endif
      if (writable) break;
      Fd_And_Timeout_User::call_handlers(ret_val);
      if (readable) break;
    }
    // interrupted - EINTR
  }
  Fd_And_Timeout_User::remove_fd(send_fd, hnd, FD_EVENT_WR);
#ifndef USE_EPOLL
  // As Fd_And_Timeout_User::call_handlers is not called:
  // received events should be cleared at this point
  // (Most probably the behavior would be correct without clearing it.)
  int nPollFds = FdMap::getSize();
  pollfd * pollFds = FdMap::getPollFds();
  for (int i = 0; i < nPollFds; ++i) pollFds[i].revents = 0;
#endif
}
