commit 8d7562cd14f446735609a0de38564ee09fbf6af3
Author: cyanide-burnout <cyanide.burnout@gmail.com>
Date:   Wed Mar 23 20:57:51 2016 +0400

    Initial import

diff --git a/CronosAgent.c b/CronosAgent.c
new file mode 100644
index 0000000..dd286ce
--- /dev/null
+++ b/CronosAgent.c
@@ -0,0 +1,696 @@
+#include <stddef.h>
+#include <stdint.h>
+
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include <netdb.h>
+#include <arpa/inet.h>
+
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include <openssl/sha.h>
+
+#ifdef __linux__
+#include <endian.h>
+#include <sys/epoll.h>
+#include <sys/timerfd.h>
+#include <sys/signalfd.h>
+#endif
+
+#ifdef __MACH__
+#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 "RA-RTP.h"
+#ifndef RA_RTP_H
+#define RTP_PROTOCOL_VERSION          2
+#define RTP_CONTROL_VERSION_SHIFT     6
+#define RTP_HEADER_MINIMAL_LENGTH     12
+#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
+#define bool               int
+#define true               1
+#define false              0
+#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)
+#endif
+
+
+#define MEDIA_PORT_COUNT   (REWIND_TYPE_ANALOG_DATA - REWIND_TYPE_SLOT_1_VOICE + 1)
+#define EVENT_LIST_LENGTH  (4 + 1 + 3 + MEDIA_PORT_COUNT)
+
+#define BUFFER_SIZE        4096
+#define EXPIRATION_TIME    20
+
+
+int main(int argc, const char* argv[])
+{
+  printf("\n");
+  printf("CronosAgent for BrandMeister DMR Master Server\n");
+  printf("Copyright 2016 Artem Prilutskiy (R3ABM, cyanide.burnout@gmail.com)\n");
+  printf("\n");
+
+  // Parameters of server
+
+  const char* serverPort = STRING(REWIND_DEFAULT_PORT);
+  const char* serverLocation = NULL;
+  const char* serverPassword = NULL;
+  struct addrinfo* serverAddress = NULL;
+
+  // Parameters of repeater
+
+  int proxyTrapPort = 162;
+  int proxyBasePort = 40000;
+
+  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' },
+    { "base-port",        required_argument, NULL, 'b' },
+    { "trap-port",        required_argument, NULL, 't' },
+    { NULL,               0,                 NULL, 0   }
+  };
+
+  int selection = 0;
+  while ((selection = getopt_long(argc, CAST(char* const*, argv), "n:r:c:w:s:p:b:t:", 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 'b':
+        proxyBasePort = strtol(optarg, NULL, 10);
+        break;
+
+      case 't':
+        proxyTrapPort = strtol(optarg, NULL, 10);
+        break;
+    }
+
+  if ((repeaterNumber == 0) ||
+      (serverPassword == NULL) ||
+      (serverLocation == NULL) ||
+      (repeaterSocketAddress.sin_addr.s_addr == htonl(INADDR_ANY)))
+  {
+    printf(
+      "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 <port number of RadioActivity Console>\n"
+      "    --base-port <base port for RTP Media>\n"
+      "    --trap-port <port for SNMP Traps>\n"
+      "\n",
+      argv[0]);
+    return EXIT_FAILURE;
+  }
+
+  // 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)
+  {
+    printf("Error resolving server address %s\n", serverLocation);
+    return EXIT_FAILURE;
+  }
+
+  // Initialize proxy sockets
+
+  int trapHandle;
+  int remoteHandle;
+  int mediaHandles[MEDIA_PORT_COUNT];
+  struct sockaddr_in proxySocketAddress;
+
+  proxySocketAddress.sin_family = AF_INET;
+  proxySocketAddress.sin_addr.s_addr = htonl(INADDR_ANY);
+
+  proxySocketAddress.sin_port = 0;
+  remoteHandle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+  if((remoteHandle < 0) ||
+     (bind(remoteHandle, (struct sockaddr*)&proxySocketAddress, sizeof(proxySocketAddress)) < 0))
+  {
+    printf("Error opening port for Remote Control\n");
+    return EXIT_FAILURE;
+  }
+
+  proxySocketAddress.sin_port = htons(proxyTrapPort);
+  trapHandle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+  if((trapHandle < 0) ||
+     (bind(trapHandle, (struct sockaddr*)&proxySocketAddress, sizeof(proxySocketAddress)) < 0))
+  {
+    printf("Error opening port for SNMP Traps\n");
+    return EXIT_FAILURE;
+  }
+
+  for (size_t index = 0; index < MEDIA_PORT_COUNT; index ++)
+  {
+    proxySocketAddress.sin_port = htons(proxyBasePort + index * 10);
+    int handle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+    if((handle < 0) ||
+       (bind(handle, (struct sockaddr*)&proxySocketAddress, sizeof(proxySocketAddress)) < 0))
+    {
+      printf("Error opening port for RTP Media\n");
+      return EXIT_FAILURE;
+    }
+    mediaHandles[index] = handle;
+  }
+
+  // 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))
+  {
+    printf("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);
+
+  for (size_t index = 0; index < MEDIA_PORT_COUNT; index ++)
+  {
+    event.events = EPOLLIN;
+    event.data.fd = mediaHandles[index];
+    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();
+
+  for (size_t index = 0; index < MEDIA_PORT_COUNT; index ++)
+  {
+    EV_SET(change, mediaHandles[index], 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 sequenceNumber = 0;
+
+  // Main loop
+
+  bool running = true;
+
+  printf("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;
+      printf("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_SLOT_1_VOICE) &&
+              (type <= REWIND_TYPE_ANALOG_DATA))
+          {
+            size_t index = type - REWIND_TYPE_SLOT_1_VOICE;
+            repeaterSocketAddress.sin_port = htons(proxyBasePort + index * 10);
+            sendto(mediaHandles[index], 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_REPORT)
+          {
+            incomingBuffer->data[length] = '\0';
+            printf("Server message: %s\n", incomingBuffer->data);
+            continue;
+          }
+
+          if (type == REWIND_TYPE_CHALLENGE)
+          {
+            printf("Authenticating with agent\n");
+
+            memcpy(incomingBuffer->data + length, serverPassword, passwordLength);
+            SHA256(incomingBuffer->data, length + passwordLength, outgoingBuffer->data);
+          
+            outgoingBuffer->type = htole16(REWIND_TYPE_AUTHENTICATION);
+            outgoingBuffer->number = htole32(++ sequenceNumber);
+            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)
+          {
+            printf("Disconnect request received\n");
+            running = false;
+            break;
+          }
+        }
+      }
+
+      // Handle packet of RTP
+
+      for (size_t index = 0; index < MEDIA_PORT_COUNT; index ++)
+      {
+        if (CHECK(event, mediaHandles[index]))
+        {
+          struct sockaddr_in address;
+          socklen_t size = sizeof(address);
+          uint8_t* buffer = (uint8_t*)outgoingBuffer->data;
+          size_t length = recvfrom(mediaHandles[index], 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] >> RTP_CONTROL_VERSION_SHIFT) == RTP_PROTOCOL_VERSION) &&
+              (length                                   >= RTP_HEADER_MINIMAL_LENGTH))
+          {
+            outgoingBuffer->type = htole16(index + REWIND_TYPE_SLOT_1_VOICE);
+            outgoingBuffer->number = htole32(++ sequenceNumber);
+            outgoingBuffer->length = htole16(length);
+            length += sizeof(struct RewindData);
+            sendto(uplinkHandle, outgoingBuffer, length, 0, serverAddress->ai_addr, serverAddress->ai_addrlen);
+          }
+          break;
+        }
+      }
+
+      // Handle packet of Remote Control
+
+      if (CHECK(event, remoteHandle))
+      {
+        struct sockaddr_in address;
+        socklen_t size = sizeof(address);
+        uint8_t* buffer = (uint8_t*)outgoingBuffer->data;
+        size_t length = recvfrom(remoteHandle, 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]               == REMOTE_STX) &&
+            (buffer[length - 1]      == REMOTE_ETX) &&
+            (length                  >= 8))
+        {
+          outgoingBuffer->type = htole16(REWIND_TYPE_REMOTE_CONTROL);
+          outgoingBuffer->number = htole32(++ sequenceNumber);
+          outgoingBuffer->length = htole16(length);
+          length += sizeof(struct RewindData);
+          sendto(uplinkHandle, outgoingBuffer, length, 0, serverAddress->ai_addr, serverAddress->ai_addrlen);
+        }
+        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->number = htole32(++ sequenceNumber);
+          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)
+        {
+          printf("Connection time-out expired\n");
+          running = false;
+          break;
+        }
+
+        size_t length = sizeof(struct RewindVersionData);
+        struct RewindVersionData* data = (struct RewindVersionData*)outgoingBuffer->data;
+        length += sprintf(data->version, "CronosAgent " STRING(VERSION) " " BUILD);
+        data->number = htole32(repeaterNumber);
+
+        outgoingBuffer->type = htole16(REWIND_TYPE_KEEP_ALIVE);
+        outgoingBuffer->number = htole32(++ sequenceNumber);
+        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->number = htole32(++ sequenceNumber);
+        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;
+        }
+      }
+    }
+  }
+
+  printf("Server stopped\n");
+
+  // Clean up
+
+  close(uplinkHandle);
+  close(remoteHandle);
+  close(trapHandle);
+
+  for (size_t index = 0; index < MEDIA_PORT_COUNT; index ++)
+  {
+    int handle = mediaHandles[index];
+    close(handle);
+  }
+
+#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;
+};
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..36f8e83
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,51 @@
+BUILD := $(shell date -u +%Y%m%d-%H%M%S)
+OS := $(shell uname -s)
+
+TOOLKIT = ../..
+
+DIRECTORIES = \
+  $(TOOLKIT)/Common \
+  $(TOOLKIT)/KAIROS
+
+ifeq ($(OS), Linux)
+  LIBRARIES += \
+    rt
+  DEPENDENCIES = \
+    openssl
+endif
+
+ifeq ($(OS), Darwin)
+  CFLAGS += \
+    -Wno-deprecated-declarations \
+    -I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-migrator/sdk/MacOSX.sdk/usr/include/
+  LIBRARIES += \
+    crypto
+endif
+
+OBJECTS = \
+  CronosAgent.o
+
+FLAGS := -g -fno-omit-frame-pointer -O3 -MMD $(foreach directory, $(DIRECTORIES), -I$(directory)) -DBUILD=\"$(BUILD)\"
+LIBS := -lstdc++ $(foreach library, $(LIBRARIES), -l$(library))
+
+CC = gcc
+CFLAGS += $(FLAGS) -std=gnu99
+
+ifeq ($(OS), Linux)
+  FLAGS += $(shell pkg-config --cflags $(DEPENDENCIES)) -rdynamic
+  LIBS += $(shell pkg-config --libs $(DEPENDENCIES))
+endif
+
+all: build
+
+build: $(PREREQUISITES) $(OBJECTS)
+	$(CC) $(OBJECTS) $(FLAGS) $(LIBS) -o cronosagent
+
+clean:
+	rm -f $(PREREQUISITES) $(OBJECTS) cronosagent
+	rm -f *.d $(TOOLKIT)/*/*.d
+
+version:
+	 echo "#define VERSION $(shell svn info | grep -E "^Revision:" | grep -o -E "[0-9]+")" > Version.h
+
+.PHONY: all build clean install
diff --git a/Rewind.h b/Rewind.h
new file mode 100644
index 0000000..90a1c82
--- /dev/null
+++ b/Rewind.h
@@ -0,0 +1,64 @@
+#ifndef REWIND_H
+#define REWIND_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#pragma pack(push, 1)
+
+#define REWIND_DEFAULT_PORT          54004
+#define REWIND_KEEP_ALIVE_INTERVAL   2
+
+#define REWIND_SIGN_LENGTH           8
+#define REWIND_PROTOCOL_SIGN         "REWIND01"
+
+#define REWIND_CLASS_REWIND_CONTROL  0x0000
+#define REWIND_CLASS_SYSTEM_CONSOLE  0x0100
+#define REWIND_CLASS_SERVER_NOTICE   0x0200
+#define REWIND_CLASS_KAIROS_DATA     0x0800
+
+#define REWIND_TYPE_KEEP_ALIVE       (REWIND_CLASS_REWIND_CONTROL + 0)
+#define REWIND_TYPE_CLOSE            (REWIND_CLASS_REWIND_CONTROL + 1)
+#define REWIND_TYPE_CHALLENGE        (REWIND_CLASS_REWIND_CONTROL + 2)
+#define REWIND_TYPE_AUTHENTICATION   (REWIND_CLASS_REWIND_CONTROL + 3)
+
+#define REWIND_TYPE_REPORT           (REWIND_CLASS_SYSTEM_CONSOLE + 0)
+
+#define REWIND_TYPE_BUSY_NOTICE      (REWIND_CLASS_SERVER_NOTICE + 0)
+
+#define REWIND_TYPE_REMOTE_CONTROL   (REWIND_CLASS_KAIROS_DATA + 0)
+#define REWIND_TYPE_SNMP_TRAP        (REWIND_CLASS_KAIROS_DATA + 1)
+#define REWIND_TYPE_SLOT_1_VOICE     (REWIND_CLASS_KAIROS_DATA + 2)
+#define REWIND_TYPE_SLOT_2_VOICE     (REWIND_CLASS_KAIROS_DATA + 3)
+#define REWIND_TYPE_ANALOG_VOICE     (REWIND_CLASS_KAIROS_DATA + 4)
+#define REWIND_TYPE_SLOT_1_DATA      (REWIND_CLASS_KAIROS_DATA + 5)
+#define REWIND_TYPE_SLOT_2_DATA      (REWIND_CLASS_KAIROS_DATA + 6)
+#define REWIND_TYPE_ANALOG_DATA      (REWIND_CLASS_KAIROS_DATA + 7)
+
+struct RewindVersionData
+{
+  uint32_t number;  // Agent ID
+  char version[0];  // Software version
+};
+
+struct RewindData
+{
+  char sign[REWIND_SIGN_LENGTH];
+  uint16_t type;    // REWIND_TYPE_*
+  uint16_t flags;   // REWIND_FLAG_*
+  uint32_t number;  // Packet sequence number
+  uint16_t length;  // Length of following data
+  uint8_t data[0];  //
+};
+
+#pragma pack(pop)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/Version.h b/Version.h
new file mode 100644
index 0000000..1623b15
--- /dev/null
+++ b/Version.h
@@ -0,0 +1 @@
+#define VERSION 6604
diff --git a/cronosagent.service b/cronosagent.service
new file mode 100644
index 0000000..e728113
--- /dev/null
+++ b/cronosagent.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=CronosAgent
+Afer=network.target
+
+[Service]
+; system.service
+Type=simple
+ExecStart=/opt/CronosAgent/cronosagent.sh
+Restart=always
+RestartSec=10
+; system.exec
+User=cronos
+Group=cronos
+StandardOutput=syslog
+WorkingDirectory=/opt/CronosAgent
+
+[Install]
+WantedBy=multi-user.target
diff --git a/cronosagent.sh b/cronosagent.sh
new file mode 100755
index 0000000..3642946
--- /dev/null
+++ b/cronosagent.sh
@@ -0,0 +1,13 @@
+#/bin/bash
+
+REPEATER_NUMBER=250304
+REPEATER_ADDRESS=172.33.20.136
+SERVER_ADDRESS=aesyle.dstar.su
+SERVER_PASSWORD=passw0rd
+
+./cronosagent \
+  --trap-port 8162 \
+  --repeater-number ${REPEATER_NUMBER} \
+  --repeater-address ${REPEATER_ADDRESS} \
+  --server-password ${SERVER_PASSWORD} \
+  --server-address ${SERVER_ADDRESS}
diff --git a/test.sh b/test.sh
new file mode 100755
index 0000000..149fda4
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,4 @@
+#/bin/bash
+make
+./cronosagent.sh
+make clean