#include <stddef.h>
#include <stdint.h>

#include <stdlib.h>
#include <stdarg.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

#include <netdb.h>
#include <arpa/inet.h>

#include <unistd.h>
#include <syslog.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/socket.h>


#include <openssl/sha.h>

#ifdef __linux__
#include <endian.h>
#include <byteswap.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <sys/signalfd.h>
#endif

#ifdef __MACH__
#include <launch.h>
#include <stdbool.h>
#include <sys/event.h>
#include <mach/mach.h>
#include <mach/clock.h>
#include <machine/endian.h>
#endif

#include "Rewind.h"
#include "Version.h"

// #include "ASNTools.h"
#ifndef ASNTOOLS_H
#define ASN_SEQUENCE      0x10
#define ASN_UNIVERSAL     0x00
#define ASN_CONSTRUCTOR   0x20
#endif

// #include "KAIROS-HAM.h"
#ifndef KAIROS_HAM_H
#define KAIROS_HAM_DEFAULT_PORT        65472
#define KAIROS_HAM_RECORD_ID           0xcbfaded8
#define KAIROS_EXCHANGE_HEADER_LENGTH  16
#endif

// #include "RemoteControl.h"
#ifndef REMOTECONTROL_H
#define REMOTE_DEFAULT_PORT  4000
#define REMOTE_STX           0x02
#define REMOTE_ETX           0x03
#endif

#define HELPER(value)      #value
#define STRING(value)      HELPER(value)

#define COUNT(array)       sizeof(array) / sizeof(array[0])

#ifdef __cplusplus
#define CAST(type, value)  const_cast<type>(value)
#else
#define CAST(type, value)  (type)value

#ifndef bool
#define bool               int
#define true               1
#define false              0
#endif
#endif

#ifdef __linux__
#define CHECK(event, handle)  (event->data.fd == handle)
#endif

#ifdef __MACH__
#define CHECK(event, handle)  (event->ident == handle) && (event->filter == EVFILT_READ)

#define htobe16(value)  OSSwapHostToBigInt16(value)
#define be16toh(value)  OSSwapBigToHostInt16(value)
#define htobe32(value)  OSSwapHostToBigInt32(value)
#define be32toh(value)  OSSwapBigToHostInt32(value)

#define htole16(value)  OSSwapHostToLittleInt16(value)
#define le16toh(value)  OSSwapLittleToHostInt16(value)
#define htole32(value)  OSSwapHostToLittleInt32(value)
#define le32toh(value)  OSSwapLittleToHostInt32(value)

#define __bswap_16(value)  OSSwapConstInt16(value)
#define __bswap_32(value)  OSSwapConstInt32(value)
#endif


#define MODE_CONSOLE (1 << 0)
#define MODE_SYSLOG  (1 << 1)
#define MODE_DAEMON  (1 << 2)

#define EVENT_LIST_LENGTH  (4 + 1 + 4)

#define BUFFER_SIZE        4096
#define EXPIRATION_TIME    20


int serviceMode = MODE_CONSOLE;

void print(const char* format, ...)
{
  va_list arguments;
  va_start(arguments, format);
  if (serviceMode & MODE_CONSOLE)
    vprintf(format, arguments);
  if (serviceMode & MODE_SYSLOG)
    vsyslog(LOG_INFO, format, arguments);
  va_end(arguments);
}

int main(int argc, const char* argv[])
{
  print("\n");
  print("CronosAgent for BrandMeister DMR Master Server\n");
  print("Copyright 2016 Artem Prilutskiy (R3ABM, cyanide.burnout@gmail.com)\n");
  print("Software revision " STRING(VERSION) " build " BUILD "\n");
  print("\n");

  // Parameters for server

  const char* serverPort = "54004";
  const char* serverLocation = NULL;
  const char* serverPassword = NULL;
  struct addrinfo* serverAddress = NULL;

  // Parameters for repeater

  int proxyTrapPort = 162;

  uint32_t repeaterNumber = 0;
  int repeaterControlPort = REMOTE_DEFAULT_PORT;

  struct sockaddr_in repeaterSocketAddress;
  repeaterSocketAddress.sin_family = AF_INET;
  repeaterSocketAddress.sin_addr.s_addr = htonl(INADDR_ANY);

  // Start up

  struct option options[] =
  {
    { "repeater-number",  required_argument, NULL, 'n' },
    { "repeater-address", required_argument, NULL, 'r' },
    { "repeater-port",    required_argument, NULL, 'c' },
    { "server-password",  required_argument, NULL, 'w' },
    { "server-address",   required_argument, NULL, 's' },
    { "server-port",      required_argument, NULL, 'p' },
    { "trap-port",        required_argument, NULL, 't' },
    { "service-mode",     required_argument, NULL, 'm' },
    { NULL,               0,                 NULL, 0   }
  };

  int selection = 0;
  while ((selection = getopt_long(argc, CAST(char* const*, argv), "n:r:c:w:s:p:t:m:", options, NULL)) != EOF)
    switch (selection)
    {
      case 'n':
        repeaterNumber = strtol(optarg, NULL, 10);
        break;

      case 'r':
        inet_pton(AF_INET, optarg, &repeaterSocketAddress.sin_addr);
        break;

      case 'c':
        repeaterControlPort = strtol(optarg, NULL, 10);
        break;

      case 'w':
        serverPassword = optarg;
        break;

      case 's':
        serverLocation = optarg;
        break;

      case 'p':
        serverPort = optarg;
        break;

      case 't':
        proxyTrapPort = strtol(optarg, NULL, 10);
        break;

      case 'm':
        serviceMode = strtol(optarg, NULL, 10);
        break;
    }

  if ((repeaterNumber == 0) ||
      (serverPassword == NULL) ||
      (serverLocation == NULL) ||
      (repeaterSocketAddress.sin_addr.s_addr == htonl(INADDR_ANY)))
  {
    print(
      "Usage:\n"
      "  %s\n"
      "    --repeater-address <IP address of repeater>\n"
      "    --repeater-number <Registered ID of repeater>\n"
      "    --repeater-port <port number of interface Remote Control>\n"
      "    --server-password <access password of BrandMeister DMR Server>\n"
      "    --server-address <domain name of BrandMeister DMR Server>\n"
      "    --server-port <local port for BrandMeister DMR Server>\n"
      "    --trap-port <port for SNMP Traps>\n"
      "    --service-mode <set of bits>\n"
      "        bit 0 - print to standard output\n"
      "        bit 1 - print to system log\n"
      "        bit 2 - run as daemon\n"
      "\n",
      argv[0]);
    return EXIT_FAILURE;
  }

#ifdef __linux__
  if ((serviceMode & MODE_DAEMON) &&
      (daemon(-1, -1) < 0))
  {
    print("Error launching daemon");
    return EXIT_FAILURE;
  }
#endif
#ifdef __MACH__
  if (serviceMode & MODE_DAEMON)
  {
    launch_data_t request = launch_data_new_string(LAUNCH_KEY_CHECKIN);
    launch_data_t response = launch_msg(request);
    launch_data_free(request);
    if (response == NULL)
    {
      print("Error calling launchd");
      return EXIT_FAILURE;
    }
    launch_data_type_t type = launch_data_get_type(response);
    launch_data_free(response);
    if (type == LAUNCH_DATA_ERRNO)
    {
      print("launchd returned error %d\n", launch_data_get_errno(response));
      return EXIT_FAILURE;
    }
    // launchd will return dictionary of job for successful check-in
    if (type != LAUNCH_DATA_DICTIONARY)
    {
      print("Error launching daemon");
      return EXIT_FAILURE;
    }
  }
#endif

  // Resolve server address

  struct addrinfo hints;

  memset(&hints, 0, sizeof(hints));
  hints.ai_socktype = SOCK_DGRAM;

#ifdef __linux__
  hints.ai_flags = AI_ADDRCONFIG;
  hints.ai_family = AF_UNSPEC;
#endif
#ifdef __MACH__
  hints.ai_flags = AI_V4MAPPED;
  hints.ai_family = AF_INET6;
#endif

  if (getaddrinfo(serverLocation, serverPort, &hints, &serverAddress) != 0)
  {
    print("Error resolving server address %s\n", serverLocation);
    return EXIT_FAILURE;
  }

  // Initialize proxy sockets

  int trapHandle;
  int mediaHandle;
  int remoteHandle;
  struct sockaddr_in proxySocketAddress;

  socklen_t proxySocketLength = sizeof(proxySocketAddress);
  int socketOptionValue = true;

  proxySocketAddress.sin_family = AF_INET;
  proxySocketAddress.sin_addr.s_addr = htonl(INADDR_ANY);

  proxySocketAddress.sin_port = htons(proxyTrapPort);
  trapHandle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if((trapHandle < 0) ||
     (bind(trapHandle, (struct sockaddr*)&proxySocketAddress, proxySocketLength) < 0))
  {
    print("Error opening port for SNMP Traps\n");
    return EXIT_FAILURE;
  }

  proxySocketAddress.sin_port = 0;
  remoteHandle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if((remoteHandle < 0) ||
     (bind(remoteHandle, (struct sockaddr*)&proxySocketAddress, proxySocketLength) < 0) ||
     (setsockopt(remoteHandle, IPPROTO_IP, IP_PKTINFO, &socketOptionValue, sizeof(socketOptionValue)) < 0))
  {
    print("Error opening port for Remote Control\n");
    return EXIT_FAILURE;
  }

  proxySocketAddress.sin_port = 0;
  mediaHandle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if((mediaHandle < 0) ||
     (bind(mediaHandle, (struct sockaddr*)&proxySocketAddress, proxySocketLength) < 0) ||
     (getsockname(mediaHandle, (struct sockaddr*)&proxySocketAddress, &proxySocketLength) < 0))
  {
    print("Error opening port for External Server\n");
    return EXIT_FAILURE;
  }

  // Initialize uplink socket

  int uplinkHandle;
  struct sockaddr_in6 uplinkSocketAddress;

  uplinkSocketAddress.sin6_family = AF_INET6;
  uplinkSocketAddress.sin6_addr = in6addr_any;
  uplinkSocketAddress.sin6_port = 0;
  uplinkSocketAddress.sin6_scope_id = 0;

  uplinkHandle = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
  if((uplinkHandle < 0) ||
     (bind(uplinkHandle, (struct sockaddr*)&uplinkSocketAddress, sizeof(uplinkSocketAddress)) < 0))
  {
    print("Error opening port for Rewind Uplink\n");
    return EXIT_FAILURE;
  }

#ifdef __linux__
  
  // Initialize timer handle

  int timerHandle;
  struct itimerspec timerInterval;

  memset(&timerInterval, 0, sizeof(timerInterval));
  timerInterval.it_interval.tv_sec = REWIND_KEEP_ALIVE_INTERVAL;
  timerInterval.it_value.tv_sec = REWIND_KEEP_ALIVE_INTERVAL;

  timerHandle = timerfd_create(CLOCK_MONOTONIC, 0);
  timerfd_settime(timerHandle, 0, &timerInterval, NULL);

  // Initialize signal handle

  int signalHandle;
  sigset_t signalMask;

  sigemptyset(&signalMask);
  sigaddset(&signalMask, SIGINT);
  sigaddset(&signalMask, SIGHUP);
  sigaddset(&signalMask, SIGTERM);
  sigaddset(&signalMask, SIGQUIT);

  sigprocmask(SIG_BLOCK, &signalMask, NULL);
  signalHandle = signalfd(-1, &signalMask, 0);

  // Initialize ePoll

  int pollHandle;
  struct epoll_event event;
  
  pollHandle = epoll_create(EVENT_LIST_LENGTH);

  event.events = EPOLLIN;
  event.data.fd = mediaHandle;
  epoll_ctl(pollHandle, EPOLL_CTL_ADD, event.data.fd, &event);

  event.events = EPOLLIN;
  event.data.fd = trapHandle;
  epoll_ctl(pollHandle, EPOLL_CTL_ADD, event.data.fd, &event);

  event.events = EPOLLIN;
  event.data.fd = remoteHandle;
  epoll_ctl(pollHandle, EPOLL_CTL_ADD, event.data.fd, &event);

  event.events = EPOLLIN;
  event.data.fd = uplinkHandle;
  epoll_ctl(pollHandle, EPOLL_CTL_ADD, event.data.fd, &event);

  event.events = EPOLLIN;
  event.data.fd = timerHandle;
  epoll_ctl(pollHandle, EPOLL_CTL_ADD, event.data.fd, &event);

  event.events = EPOLLIN;
  event.data.fd = signalHandle;
  epoll_ctl(pollHandle, EPOLL_CTL_ADD, event.data.fd, &event);

  // Initialize clock

  struct timespec now;
  clock_gettime(CLOCK_MONOTONIC, &now);

#endif
#ifdef __MACH__

  // Prepare signal handlers

  signal(SIGINT, SIG_IGN);
  signal(SIGHUP, SIG_IGN);
  signal(SIGTERM, SIG_IGN);
  signal(SIGQUIT, SIG_IGN);

  // Initialize KQueue

  int queueHandle;

  struct kevent changes[EVENT_LIST_LENGTH];
  struct kevent* change = changes;

  queueHandle = kqueue();

  EV_SET(change, mediaHandle, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
  change ++;

  EV_SET(change, uplinkHandle, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
  change ++;

  EV_SET(change, remoteHandle, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
  change ++;

  EV_SET(change, trapHandle, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
  change ++;

  EV_SET(change, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, NOTE_SECONDS, REWIND_KEEP_ALIVE_INTERVAL, 0);
  change ++;

  EV_SET(change, SIGINT, EVFILT_SIGNAL, EV_ADD | EV_ENABLE, 0, 0, 0);
  change ++;

  EV_SET(change, SIGHUP, EVFILT_SIGNAL, EV_ADD | EV_ENABLE, 0, 0, 0);
  change ++;

  EV_SET(change, SIGTERM, EVFILT_SIGNAL, EV_ADD | EV_ENABLE, 0, 0, 0);
  change ++;

  EV_SET(change, SIGQUIT, EVFILT_SIGNAL, EV_ADD | EV_ENABLE, 0, 0, 0);
  change ++;

  // Initialize clock service

  clock_serv_t clockService;
  mach_timespec_t now;

  host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &clockService);
  clock_get_time(clockService, &now);

#endif

  // Prepare uplink buffers

  struct RewindData* incomingBuffer = (struct RewindData*)alloca(sizeof(struct RewindData) + BUFFER_SIZE);
  struct RewindData* outgoingBuffer = (struct RewindData*)alloca(sizeof(struct RewindData) + BUFFER_SIZE);

  memset(outgoingBuffer, 0, sizeof(struct RewindData));
  memcpy(outgoingBuffer, REWIND_PROTOCOL_SIGN, REWIND_SIGN_LENGTH);

  size_t passwordLength = strlen(serverPassword);
  time_t watchDog = now.tv_sec + EXPIRATION_TIME;

  uint32_t sequenceNumbers[] = 
  {
    0,
    0,
    0,
    0
  };

  // Main loop

  bool running = true;

  print("Server started\n");

  while (running)
  {
#ifdef __linux__
    struct epoll_event events[EVENT_LIST_LENGTH];
    int count = epoll_wait(pollHandle, events, EVENT_LIST_LENGTH, -1);
#endif
#ifdef __MACH__
    struct kevent events[EVENT_LIST_LENGTH];
    int count = kevent(queueHandle, changes, EVENT_LIST_LENGTH, events, EVENT_LIST_LENGTH, NULL);
#endif

    if (count < 0)
    {
      int error = errno;
      print("Error processing handles: %s (%d)\n", strerror(error), error);
      break;
    }

    for (size_t index = 0; index < count; index ++)
    {
#ifdef __linux__
      struct epoll_event* event = events + index;
#endif
#ifdef __MACH__
      struct kevent* event = events + index;
#endif

      // Handle packet from the uplink

      if (CHECK(event, uplinkHandle))
      {
        struct sockaddr_in6 address;
        socklen_t size = sizeof(address);
        size_t length = recvfrom(uplinkHandle, incomingBuffer, BUFFER_SIZE, 0, (struct sockaddr*)&address, &size);

        if ((length >= sizeof(struct RewindData)) &&
            ((serverAddress->ai_addr->sa_family == AF_INET) ||  /* Work-around for Linux */
             (memcmp(&address, serverAddress->ai_addr, serverAddress->ai_addrlen) == 0)) &&
             (memcmp(incomingBuffer->sign, REWIND_PROTOCOL_SIGN, REWIND_SIGN_LENGTH) == 0))
        {
          uint16_t type = le16toh(incomingBuffer->type);
          size_t length = le16toh(incomingBuffer->length);

          if (type == REWIND_TYPE_EXTERNAL_SERVER)
          {
            repeaterSocketAddress.sin_port = htons(KAIROS_HAM_DEFAULT_PORT);
            sendto(mediaHandle, incomingBuffer->data, length, 0, (struct sockaddr*)&repeaterSocketAddress, sizeof(struct sockaddr_in));
            continue;
          }

          if (type == REWIND_TYPE_REMOTE_CONTROL)
          {
            repeaterSocketAddress.sin_port = htons(repeaterControlPort);
            sendto(remoteHandle, incomingBuffer->data, length, 0, (struct sockaddr*)&repeaterSocketAddress, sizeof(struct sockaddr_in));
            continue;
          }

          if (type == REWIND_TYPE_ADDRESS_NOTICE)
          {
            socketOptionValue = false;
            setsockopt(remoteHandle, IPPROTO_IP, IP_PKTINFO, &socketOptionValue, sizeof(socketOptionValue));
            continue;
          }

          if (type == REWIND_TYPE_REPORT)
          {
            incomingBuffer->data[length] = '\0';
            print("Server message: %s\n", incomingBuffer->data);
            continue;
          }

          if (type == REWIND_TYPE_CHALLENGE)
          {
            print("Authenticating with agent\n");

            memcpy(incomingBuffer->data + length, serverPassword, passwordLength);
            SHA256(incomingBuffer->data, length + passwordLength, outgoingBuffer->data);
            outgoingBuffer->type = htole16(REWIND_TYPE_AUTHENTICATION);
            outgoingBuffer->flags = htole16(REWIND_FLAG_DEFAULT_SET);
            outgoingBuffer->number = htole32(++ sequenceNumbers[0]);
            outgoingBuffer->length = htole16(SHA256_DIGEST_LENGTH);

            sendto(uplinkHandle, outgoingBuffer, sizeof(struct RewindData) + SHA256_DIGEST_LENGTH, 0, serverAddress->ai_addr, serverAddress->ai_addrlen);
            continue;
          }

          if (type == REWIND_TYPE_KEEP_ALIVE)
          {
#ifdef __linux__
            clock_gettime(CLOCK_MONOTONIC, &now);
#endif
#ifdef __MACH__
            clock_get_time(clockService, &now);
#endif
            watchDog = now.tv_sec + EXPIRATION_TIME;
            continue;
          }

          if (type == REWIND_TYPE_CLOSE)
          {
            print("Disconnect request received\n");
            running = false;
            break;
          }
        }
      }

      // Handle packet of External Server

      if (CHECK(event, mediaHandle))
      {
        struct sockaddr_in address;
        socklen_t size = sizeof(address);
        uint8_t* buffer = (uint8_t*)outgoingBuffer->data;
        size_t length = recvfrom(mediaHandle, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&address, &size);

        if ((size                        == sizeof(struct sockaddr_in)) &&
            (address.sin_addr.s_addr     == repeaterSocketAddress.sin_addr.s_addr) &&
            (length                      >= KAIROS_EXCHANGE_HEADER_LENGTH) &&
            (le32toh(*(uint32_t*)buffer) == KAIROS_HAM_RECORD_ID))
        {
          int value = buffer[8] & 3;
          outgoingBuffer->type = htole16(REWIND_TYPE_EXTERNAL_SERVER);
          outgoingBuffer->flags = htole16(value);
          outgoingBuffer->number = htole32(++ sequenceNumbers[value]);
          outgoingBuffer->length = htole16(length);
          length += sizeof(struct RewindData);
          sendto(uplinkHandle, outgoingBuffer, length, 0, serverAddress->ai_addr, serverAddress->ai_addrlen);
        }

        continue;
      }

      // Handle packet of Remote Control

      if (CHECK(event, remoteHandle))
      {
        struct sockaddr_in address;
        uint8_t* buffer = (uint8_t*)outgoingBuffer->data;

        struct iovec vector;
        vector.iov_base = buffer;
        vector.iov_len = BUFFER_SIZE;

        struct msghdr message;
        message.msg_name = &address;
        message.msg_namelen = sizeof(address);
        message.msg_iov = &vector;
        message.msg_iovlen = 1;
        message.msg_control = alloca(BUFFER_SIZE);
        message.msg_controllen = BUFFER_SIZE;
        message.msg_flags = 0;

        size_t length = recvmsg(remoteHandle, &message, 0);

        if ((message.msg_namelen     == sizeof(struct sockaddr_in)) &&
            (address.sin_addr.s_addr == repeaterSocketAddress.sin_addr.s_addr) &&
            (buffer[0]               == REMOTE_STX) &&
            (buffer[length - 1]      == REMOTE_ETX) &&
            (length                  >= 8))
        {
          outgoingBuffer->type = htole16(REWIND_TYPE_REMOTE_CONTROL);
          outgoingBuffer->flags = htole16(REWIND_FLAG_DEFAULT_SET);
          outgoingBuffer->number = htole32(++ sequenceNumbers[0]);
          outgoingBuffer->length = htole16(length);
          length += sizeof(struct RewindData);
          sendto(uplinkHandle, outgoingBuffer, length, 0, serverAddress->ai_addr, serverAddress->ai_addrlen);

          // Transmit destination address of received packet
          // to make correct messages for External Server interface

          struct cmsghdr* control = CMSG_FIRSTHDR(&message);
          while (control != NULL)
          {
            if (control->cmsg_type == IP_PKTINFO)
            {
              struct in_pktinfo* information = (struct in_pktinfo*)CMSG_DATA(control);

              size_t length = sizeof(struct RewindAddressData);
              struct RewindAddressData* data = (struct RewindAddressData*)outgoingBuffer->data;
              data->address.s_addr = information->ipi_addr.s_addr;
              data->port = __bswap_16(proxySocketAddress.sin_port);

              outgoingBuffer->type = htole16(REWIND_TYPE_ADDRESS_NOTICE);
              outgoingBuffer->flags = htole16(REWIND_FLAG_DEFAULT_SET);
              outgoingBuffer->number = htole32(++ sequenceNumbers[0]);
              outgoingBuffer->length = htole16(length);
              length += sizeof(struct RewindData);
              sendto(uplinkHandle, outgoingBuffer, length, 0, serverAddress->ai_addr, serverAddress->ai_addrlen);

              break;
            }
            control = CMSG_NXTHDR(&message, control);
          }
        }

        continue;
      }

      // Handle packet of SNMP Trap

      if (CHECK(event, trapHandle))
      {
        struct sockaddr_in address;
        socklen_t size = sizeof(address);
        uint8_t* buffer = (uint8_t*)outgoingBuffer->data;
        size_t length = recvfrom(trapHandle, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&address, &size);

        if ((size                    == sizeof(struct sockaddr_in)) &&
            (address.sin_addr.s_addr == repeaterSocketAddress.sin_addr.s_addr) &&
            (buffer[0]               == (ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE)) &&
            (length                  >= 20))
        {
          outgoingBuffer->type = htole16(REWIND_TYPE_SNMP_TRAP);
          outgoingBuffer->flags = htole16(REWIND_FLAG_DEFAULT_SET);
          outgoingBuffer->number = htole32(++ sequenceNumbers[0]);
          outgoingBuffer->length = htole16(length);
          length += sizeof(struct RewindData);
          sendto(uplinkHandle, outgoingBuffer, length, 0, serverAddress->ai_addr, serverAddress->ai_addrlen);
        }

        continue;
      }

      // Handle timer to transmit keep-alive

#ifdef __linux__
      if (event->data.fd == timerHandle)
      {
        uint64_t information;
        read(timerHandle, &information, sizeof(information));

        clock_gettime(CLOCK_MONOTONIC, &now);
#endif
#ifdef __MACH__
      if (event->filter == EVFILT_TIMER)
      {
        clock_get_time(clockService, &now);
#endif
        if (now.tv_sec > watchDog)
        {
          print("Connection time-out expired\n");
          running = false;
          break;
        }

        size_t length = sizeof(struct RewindVersionData);
        struct RewindVersionData* data = (struct RewindVersionData*)outgoingBuffer->data;

        data->number = htole32(repeaterNumber);
        data->service = REWIND_SERVICE_CRONOS_AGENT;
        length += sprintf(data->description, "CronosAgent " STRING(VERSION) " " BUILD);

        outgoingBuffer->type = htole16(REWIND_TYPE_KEEP_ALIVE);
        outgoingBuffer->flags = htole16(REWIND_FLAG_DEFAULT_SET);
        outgoingBuffer->number = htole32(++ sequenceNumbers[0]);
        outgoingBuffer->length = htobe16(length);
        length += sizeof(struct RewindData);
        sendto(uplinkHandle, outgoingBuffer, length, 0, serverAddress->ai_addr, serverAddress->ai_addrlen);

        continue;
      }

      // Handle signal from the kernel

#ifdef __linux__
      if (event->data.fd == signalHandle)
      {
        struct signalfd_siginfo information;
        read(signalHandle, &information, sizeof(information));
#endif
#ifdef __MACH__
      if (event->filter == EVFILT_SIGNAL)
      {
#endif
        outgoingBuffer->type = htole16(REWIND_TYPE_CLOSE);
        outgoingBuffer->flags = htole16(REWIND_FLAG_DEFAULT_SET);
        outgoingBuffer->number = htole32(++ sequenceNumbers[0]);
        outgoingBuffer->length = 0;
        sendto(uplinkHandle, outgoingBuffer, sizeof(struct RewindData), 0, serverAddress->ai_addr, serverAddress->ai_addrlen);
#ifdef __linux__
        if ((information.ssi_signo == SIGINT) ||
            (information.ssi_signo == SIGTERM) ||
            (information.ssi_signo == SIGQUIT))
#endif
#ifdef __MACH__
        if ((event->ident == SIGINT) ||
            (event->ident == SIGTERM) ||
            (event->ident == SIGQUIT))
#endif
        {
          running = false;
          break;
        }

        // SIGHUP

        socketOptionValue = true;
        setsockopt(remoteHandle, IPPROTO_IP, IP_PKTINFO, &socketOptionValue, sizeof(socketOptionValue));
      }
    }
  }

  print("Server stopped\n");

  // Clean up

  close(uplinkHandle);
  close(remoteHandle);
  close(mediaHandle);
  close(trapHandle);

#ifdef __linux__
  close(pollHandle);
  close(timerHandle);
  close(signalHandle);
#endif
#ifdef __MACH__
  close(queueHandle);
  mach_port_deallocate(mach_task_self(), clockService);
#endif

  freeaddrinfo(serverAddress);

  return EXIT_SUCCESS;
};