/*
  chronyd/chronyc - Programs for keeping computer clocks accurate.

 **********************************************************************
 * Copyright (C) Miroslav Lichvar  2020-2021
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 **********************************************************************

  =======================================================================

  NTS-KE session used by server and client
  */

#include "config.h"

#include "sysincl.h"

#include "nts_ke_session.h"

#include "conf.h"
#include "local.h"
#include "logging.h"
#include "memory.h"
#include "siv.h"
#include "socket.h"
#include "sched.h"
#include "util.h"

#include <gnutls/gnutls.h>
#include <gnutls/x509.h>

#define INVALID_SOCK_FD (-8)

struct RecordHeader {
  uint16_t type;
  uint16_t body_length;
};

struct Message {
  int length;
  int sent;
  int parsed;
  int complete;
  unsigned char data[NKE_MAX_MESSAGE_LENGTH];
};

typedef enum {
  KE_WAIT_CONNECT,
  KE_HANDSHAKE,
  KE_SEND,
  KE_RECEIVE,
  KE_SHUTDOWN,
  KE_STOPPED,
} KeState;

struct NKSN_Instance_Record {
  int server;
  char *server_name;
  NKSN_MessageHandler handler;
  void *handler_arg;

  KeState state;
  int sock_fd;
  char *label;
  gnutls_session_t tls_session;
  SCH_TimeoutID timeout_id;
  int retry_factor;

  struct Message message;
  int new_message;
};

/* ================================================== */

static gnutls_priority_t priority_cache;

static int credentials_counter = 0;

static int clock_updates = 0;

/* ================================================== */

static void
reset_message(struct Message *message)
{
  message->length = 0;
  message->sent = 0;
  message->parsed = 0;
  message->complete = 0;
}

/* ================================================== */

static int
add_record(struct Message *message, int critical, int type, const void *body, int body_length)
{
  struct RecordHeader header;

  assert(message->length <= sizeof (message->data));

  if (body_length < 0 || body_length > 0xffff || type < 0 || type > 0x7fff ||
      message->length + sizeof (header) + body_length > sizeof (message->data))
    return 0;

  header.type = htons(!!critical * NKE_RECORD_CRITICAL_BIT | type);
  header.body_length = htons(body_length);

  memcpy(&message->data[message->length], &header, sizeof (header));
  message->length += sizeof (header);

  if (body_length > 0) {
    memcpy(&message->data[message->length], body, body_length);
    message->length += body_length;
  }

  return 1;
}

/* ================================================== */

static void
reset_message_parsing(struct Message *message)
{
  message->parsed = 0;
}

/* ================================================== */

static int
get_record(struct Message *message, int *critical, int *type, int *body_length,
           void *body, int buffer_length)
{
  struct RecordHeader header;
  int blen, rlen;

  if (message->length < message->parsed + sizeof (header) ||
      buffer_length < 0)
    return 0;

  memcpy(&header, &message->data[message->parsed], sizeof (header));

  blen = ntohs(header.body_length);
  rlen = sizeof (header) + blen;
  assert(blen >= 0 && rlen > 0);

  if (message->length < message->parsed + rlen)
    return 0;

  if (critical)
    *critical = !!(ntohs(header.type) & NKE_RECORD_CRITICAL_BIT);
  if (type)
    *type = ntohs(header.type) & ~NKE_RECORD_CRITICAL_BIT;
  if (body)
    memcpy(body, &message->data[message->parsed + sizeof (header)], MIN(buffer_length, blen));
  if (body_length)
    *body_length = blen;

  message->parsed += rlen;

  return 1;
}

/* ================================================== */

static int
check_message_format(struct Message *message, int eof)
{
  int critical = 0, type = -1, length = -1, ends = 0;

  reset_message_parsing(message);
  message->complete = 0;

  while (get_record(message, &critical, &type, &length, NULL, 0)) {
    if (type == NKE_RECORD_END_OF_MESSAGE) {
      if (!critical || length != 0 || ends > 0)
        return 0;
      ends++;
    }
  }

  /* If the message cannot be fully parsed, but more data may be coming,
     consider the format to be ok */
  if (message->length == 0 || message->parsed < message->length)
    return !eof;

  if (type != NKE_RECORD_END_OF_MESSAGE)
    return !eof;

  message->complete = 1;

  return 1;
}

/* ================================================== */

static gnutls_session_t
create_tls_session(int server_mode, int sock_fd, const char *server_name,
                   gnutls_certificate_credentials_t credentials,
                   gnutls_priority_t priority)
{
  unsigned char alpn_name[sizeof (NKE_ALPN_NAME)];
  gnutls_session_t session;
  gnutls_datum_t alpn;
  unsigned int flags;
  int r;

  r = gnutls_init(&session, GNUTLS_NONBLOCK | GNUTLS_NO_TICKETS |
                  (server_mode ? GNUTLS_SERVER : GNUTLS_CLIENT));
  if (r < 0) {
    LOG(LOGS_ERR, "Could not %s TLS session : %s", "create", gnutls_strerror(r));
    return NULL;
  }

  if (!server_mode) {
    assert(server_name);

    if (!UTI_IsStringIP(server_name)) {
      r = gnutls_server_name_set(session, GNUTLS_NAME_DNS, server_name, strlen(server_name));
      if (r < 0)
        goto error;
    }

    flags = 0;

    if (clock_updates < CNF_GetNoCertTimeCheck()) {
      flags |= GNUTLS_VERIFY_DISABLE_TIME_CHECKS | GNUTLS_VERIFY_DISABLE_TRUSTED_TIME_CHECKS;
      DEBUG_LOG("Disabled time checks");
    }

    gnutls_session_set_verify_cert(session, server_name, flags);
  }

  r = gnutls_priority_set(session, priority);
  if (r < 0)
    goto error;

  r = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, credentials);
  if (r < 0)
    goto error;

  memcpy(alpn_name, NKE_ALPN_NAME, sizeof (alpn_name));
  alpn.data = alpn_name;
  alpn.size = sizeof (alpn_name) - 1;

  r = gnutls_alpn_set_protocols(session, &alpn, 1, 0);
  if (r < 0)
    goto error;

  gnutls_transport_set_int(session, sock_fd);

  return session;

error:
  LOG(LOGS_ERR, "Could not %s TLS session : %s", "set", gnutls_strerror(r));
  gnutls_deinit(session);
  return NULL;
}

/* ================================================== */

static void
stop_session(NKSN_Instance inst)
{
  if (inst->state == KE_STOPPED)
    return;

  inst->state = KE_STOPPED;

  SCH_RemoveFileHandler(inst->sock_fd);
  SCK_CloseSocket(inst->sock_fd);
  inst->sock_fd = INVALID_SOCK_FD;

  Free(inst->label);
  inst->label = NULL;

  gnutls_deinit(inst->tls_session);
  inst->tls_session = NULL;

  SCH_RemoveTimeout(inst->timeout_id);
  inst->timeout_id = 0;
}

/* ================================================== */

static void
session_timeout(void *arg)
{
  NKSN_Instance inst = arg;

  LOG(inst->server ? LOGS_DEBUG : LOGS_ERR, "NTS-KE session with %s timed out", inst->label);

  inst->timeout_id = 0;
  stop_session(inst);
}

/* ================================================== */

static int
check_alpn(NKSN_Instance inst)
{
  gnutls_datum_t alpn;

  if (gnutls_alpn_get_selected_protocol(inst->tls_session, &alpn) < 0 ||
      alpn.size != sizeof (NKE_ALPN_NAME) - 1 ||
      memcmp(alpn.data, NKE_ALPN_NAME, sizeof (NKE_ALPN_NAME) - 1) != 0)
    return 0;

  return 1;
}

/* ================================================== */

static void
set_input_output(NKSN_Instance inst, int output)
{
  SCH_SetFileHandlerEvent(inst->sock_fd, SCH_FILE_INPUT, !output);
  SCH_SetFileHandlerEvent(inst->sock_fd, SCH_FILE_OUTPUT, output);
}

/* ================================================== */

static void
change_state(NKSN_Instance inst, KeState state)
{
  int output;

  switch (state) {
    case KE_HANDSHAKE:
      output = !inst->server;
      break;
    case KE_WAIT_CONNECT:
    case KE_SEND:
    case KE_SHUTDOWN:
      output = 1;
      break;
    case KE_RECEIVE:
      output = 0;
      break;
    default:
      assert(0);
  }

  set_input_output(inst, output);

  inst->state = state;
}

/* ================================================== */

static int
handle_event(NKSN_Instance inst, int event)
{
  struct Message *message = &inst->message;
  int r;

  DEBUG_LOG("Session event %d fd=%d state=%d", event, inst->sock_fd, (int)inst->state);

  switch (inst->state) {
    case KE_WAIT_CONNECT:
      /* Check if connect() succeeded */
      if (event != SCH_FILE_OUTPUT)
        return 0;

      /* Get the socket error */
      if (!SCK_GetIntOption(inst->sock_fd, SOL_SOCKET, SO_ERROR, &r))
        r = EINVAL;

      if (r != 0) {
        LOG(LOGS_ERR, "Could not connect to %s : %s", inst->label, strerror(r));
        stop_session(inst);
        return 0;
      }

      DEBUG_LOG("Connected to %s", inst->label);

      change_state(inst, KE_HANDSHAKE);
      return 0;

    case KE_HANDSHAKE:
      r = gnutls_handshake(inst->tls_session);

      if (r < 0) {
        if (gnutls_error_is_fatal(r)) {
          gnutls_datum_t cert_error;

          /* Get a description of verification errors */
          if (r != GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR ||
              gnutls_certificate_verification_status_print(
                          gnutls_session_get_verify_cert_status(inst->tls_session),
                          gnutls_certificate_type_get(inst->tls_session), &cert_error, 0) < 0)
            cert_error.data = NULL;

          LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
              "TLS handshake with %s failed : %s%s%s", inst->label, gnutls_strerror(r),
              cert_error.data ? " " : "", cert_error.data ? (const char *)cert_error.data : "");

          if (cert_error.data)
            gnutls_free(cert_error.data);

          stop_session(inst);

          /* Increase the retry interval if the handshake did not fail due
             to the other end closing the connection */
          if (r != GNUTLS_E_PULL_ERROR && r != GNUTLS_E_PREMATURE_TERMINATION)
            inst->retry_factor = NKE_RETRY_FACTOR2_TLS;

          return 0;
        }

        /* Disable output when the handshake is trying to receive data */
        set_input_output(inst, gnutls_record_get_direction(inst->tls_session));
        return 0;
      }

      inst->retry_factor = NKE_RETRY_FACTOR2_TLS;

      if (DEBUG) {
        char *description = gnutls_session_get_desc(inst->tls_session);
        DEBUG_LOG("Handshake with %s completed %s",
                  inst->label, description ? description : "");
        gnutls_free(description);
      }

      if (!check_alpn(inst)) {
        LOG(inst->server ? LOGS_DEBUG : LOGS_ERR, "NTS-KE not supported by %s", inst->label);
        stop_session(inst);
        return 0;
      }

      /* Client will send a request to the server */
      change_state(inst, inst->server ? KE_RECEIVE : KE_SEND);
      return 0;

    case KE_SEND:
      assert(inst->new_message && message->complete);
      assert(message->length <= sizeof (message->data) && message->length > message->sent);

      r = gnutls_record_send(inst->tls_session, &message->data[message->sent],
                             message->length - message->sent);

      if (r < 0) {
        if (gnutls_error_is_fatal(r)) {
          LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
              "Could not send NTS-KE message to %s : %s", inst->label, gnutls_strerror(r));
          stop_session(inst);
        }
        return 0;
      }

      DEBUG_LOG("Sent %d bytes to %s", r, inst->label);

      message->sent += r;
      if (message->sent < message->length)
        return 0;

      /* Client will receive a response */
      change_state(inst, inst->server ? KE_SHUTDOWN : KE_RECEIVE);
      reset_message(&inst->message);
      inst->new_message = 0;
      return 0;

    case KE_RECEIVE:
      do {
        if (message->length >= sizeof (message->data)) {
          DEBUG_LOG("Message is too long");
          stop_session(inst);
          return 0;
        }

        r = gnutls_record_recv(inst->tls_session, &message->data[message->length],
                               sizeof (message->data) - message->length);

        if (r < 0) {
          /* Handle a renegotiation request on both client and server as
             a protocol error */
          if (gnutls_error_is_fatal(r) || r == GNUTLS_E_REHANDSHAKE) {
            LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
                "Could not receive NTS-KE message from %s : %s",
                inst->label, gnutls_strerror(r));
            stop_session(inst);
          }
          return 0;
        }

        DEBUG_LOG("Received %d bytes from %s", r, inst->label);

        message->length += r;

      } while (gnutls_record_check_pending(inst->tls_session) > 0);

      if (!check_message_format(message, r == 0)) {
        LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
            "Received invalid NTS-KE message from %s", inst->label);
        stop_session(inst);
        return 0;
      }

      /* Wait for more data if the message is not complete yet */
      if (!message->complete)
        return 0;

      /* Server will send a response to the client */
      change_state(inst, inst->server ? KE_SEND : KE_SHUTDOWN);

      /* Return success to process the received message */
      return 1;

    case KE_SHUTDOWN:
      r = gnutls_bye(inst->tls_session, GNUTLS_SHUT_RDWR);

      if (r < 0) {
        if (gnutls_error_is_fatal(r)) {
          DEBUG_LOG("Shutdown with %s failed : %s", inst->label, gnutls_strerror(r));
          stop_session(inst);
          return 0;
        }

        /* Disable output when the TLS shutdown is trying to receive data */
        set_input_output(inst, gnutls_record_get_direction(inst->tls_session));
        return 0;
      }

      SCK_ShutdownConnection(inst->sock_fd);
      stop_session(inst);

      DEBUG_LOG("Shutdown completed");
      return 0;

    default:
      assert(0);
      return 0;
  }
}

/* ================================================== */

static void
read_write_socket(int fd, int event, void *arg)
{
  NKSN_Instance inst = arg;

  if (!handle_event(inst, event))
    return;

  /* A valid message was received.  Call the handler to process the message,
     and prepare a response if it is a server. */

  reset_message_parsing(&inst->message);

  if (!(inst->handler)(inst->handler_arg)) {
    stop_session(inst);
    return;
  }
}

/* ================================================== */

static time_t
get_time(time_t *t)
{
  struct timespec now;

  LCL_ReadCookedTime(&now, NULL);
  if (t)
    *t = now.tv_sec;

  return now.tv_sec;
}

/* ================================================== */

static void
handle_step(struct timespec *raw, struct timespec *cooked, double dfreq,
            double doffset, LCL_ChangeType change_type, void *anything)
{
  if (change_type != LCL_ChangeUnknownStep && clock_updates < INT_MAX)
    clock_updates++;
}

/* ================================================== */

static int gnutls_initialised = 0;

static int
init_gnutls(void)
{
  int r;

  if (gnutls_initialised)
    return 1;

  r = gnutls_global_init();
  if (r < 0)
    LOG_FATAL("Could not initialise %s : %s", "gnutls", gnutls_strerror(r));

  /* Prepare a priority cache for server and client NTS-KE sessions
     (the NTS specification requires TLS1.3 or later) */
  r = gnutls_priority_init2(&priority_cache,
                            "-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.2:-VERS-DTLS-ALL",
                            NULL, GNUTLS_PRIORITY_INIT_DEF_APPEND);
  if (r < 0) {
    LOG(LOGS_ERR, "Could not initialise %s : %s",
        "priority cache for TLS", gnutls_strerror(r));
    gnutls_global_deinit();
    return 0;
  }

  /* Use our clock instead of the system clock in certificate verification */
  gnutls_global_set_time_function(get_time);

  gnutls_initialised = 1;
  DEBUG_LOG("Initialised");

  LCL_AddParameterChangeHandler(handle_step, NULL);

  return 1;
}

/* ================================================== */

static void
deinit_gnutls(void)
{
  if (!gnutls_initialised || credentials_counter > 0)
    return;

  LCL_RemoveParameterChangeHandler(handle_step, NULL);

  gnutls_priority_deinit(priority_cache);
  gnutls_global_deinit();
  gnutls_initialised = 0;
  DEBUG_LOG("Deinitialised");
}

/* ================================================== */

static NKSN_Credentials
create_credentials(const char **certs, const char **keys, int n_certs_keys,
                   const char **trusted_certs, uint32_t *trusted_certs_ids,
                   int n_trusted_certs, uint32_t trusted_cert_set)
{
  gnutls_certificate_credentials_t credentials = NULL;
  int i, r;

  if (!init_gnutls())
    return NULL;

  r = gnutls_certificate_allocate_credentials(&credentials);
  if (r < 0)
    goto error;

  if (certs && keys) {
    if (trusted_certs || trusted_certs_ids)
      assert(0);

    for (i = 0; i < n_certs_keys; i++) {
      r = gnutls_certificate_set_x509_key_file(credentials, certs[i], keys[i],
                                               GNUTLS_X509_FMT_PEM);
      if (r < 0)
        goto error;
    }
  } else {
    if (certs || keys || n_certs_keys > 0)
      assert(0);

    if (trusted_cert_set == 0 && !CNF_GetNoSystemCert()) {
      r = gnutls_certificate_set_x509_system_trust(credentials);
      if (r < 0)
        goto error;
    }

    if (trusted_certs && trusted_certs_ids) {
      for (i = 0; i < n_trusted_certs; i++) {
        struct stat buf;

        if (trusted_certs_ids[i] != trusted_cert_set)
          continue;

        if (stat(trusted_certs[i], &buf) == 0 && S_ISDIR(buf.st_mode))
          r = gnutls_certificate_set_x509_trust_dir(credentials, trusted_certs[i],
                                                    GNUTLS_X509_FMT_PEM);
        else
          r = gnutls_certificate_set_x509_trust_file(credentials, trusted_certs[i],
                                                     GNUTLS_X509_FMT_PEM);
        if (r < 0)
          goto error;

        DEBUG_LOG("Added %d trusted certs from %s", r, trusted_certs[i]);
      }
    }
  }

  credentials_counter++;

  return (NKSN_Credentials)credentials;

error:
  LOG(LOGS_ERR, "Could not set credentials : %s", gnutls_strerror(r));
  if (credentials)
    gnutls_certificate_free_credentials(credentials);
  deinit_gnutls();
  return NULL;
}

/* ================================================== */

NKSN_Credentials
NKSN_CreateServerCertCredentials(const char **certs, const char **keys, int n_certs_keys)
{
  return create_credentials(certs, keys, n_certs_keys, NULL, NULL, 0, 0);
}

/* ================================================== */

NKSN_Credentials
NKSN_CreateClientCertCredentials(const char **certs, uint32_t *ids,
                                 int n_certs_ids, uint32_t trusted_cert_set)
{
  return create_credentials(NULL, NULL, 0, certs, ids, n_certs_ids, trusted_cert_set);
}

/* ================================================== */

void
NKSN_DestroyCertCredentials(NKSN_Credentials credentials)
{
  gnutls_certificate_free_credentials((gnutls_certificate_credentials_t)credentials);
  credentials_counter--;
  deinit_gnutls();
}

/* ================================================== */

NKSN_Instance
NKSN_CreateInstance(int server_mode, const char *server_name,
                    NKSN_MessageHandler handler, void *handler_arg)
{
  NKSN_Instance inst;

  inst = MallocNew(struct NKSN_Instance_Record);

  inst->server = server_mode;
  inst->server_name = server_name ? Strdup(server_name) : NULL;
  inst->handler = handler;
  inst->handler_arg = handler_arg;
  /* Replace a NULL argument with the session itself */
  if (!inst->handler_arg)
    inst->handler_arg = inst;

  inst->state = KE_STOPPED;
  inst->sock_fd = INVALID_SOCK_FD;
  inst->label = NULL;
  inst->tls_session = NULL;
  inst->timeout_id = 0;
  inst->retry_factor = NKE_RETRY_FACTOR2_CONNECT;

  return inst;
}

/* ================================================== */

void
NKSN_DestroyInstance(NKSN_Instance inst)
{
  stop_session(inst);

  Free(inst->server_name);
  Free(inst);
}

/* ================================================== */

int
NKSN_StartSession(NKSN_Instance inst, int sock_fd, const char *label,
                  NKSN_Credentials credentials, double timeout)
{
  assert(inst->state == KE_STOPPED);

  inst->tls_session = create_tls_session(inst->server, sock_fd, inst->server_name,
                                         (gnutls_certificate_credentials_t)credentials,
                                         priority_cache);
  if (!inst->tls_session)
    return 0;

  inst->sock_fd = sock_fd;
  SCH_AddFileHandler(sock_fd, SCH_FILE_INPUT, read_write_socket, inst);

  inst->label = Strdup(label);
  inst->timeout_id = SCH_AddTimeoutByDelay(timeout, session_timeout, inst);
  inst->retry_factor = NKE_RETRY_FACTOR2_CONNECT;

  reset_message(&inst->message);
  inst->new_message = 0;

  change_state(inst, inst->server ? KE_HANDSHAKE : KE_WAIT_CONNECT);

  return 1;
}

/* ================================================== */

void
NKSN_BeginMessage(NKSN_Instance inst)
{
  reset_message(&inst->message);
  inst->new_message = 1;
}

/* ================================================== */

int
NKSN_AddRecord(NKSN_Instance inst, int critical, int type, const void *body, int body_length)
{
  assert(inst->new_message && !inst->message.complete);
  assert(type != NKE_RECORD_END_OF_MESSAGE);

  return add_record(&inst->message, critical, type, body, body_length);
}

/* ================================================== */

int
NKSN_EndMessage(NKSN_Instance inst)
{
  assert(!inst->message.complete);

  /* Terminate the message */
  if (!add_record(&inst->message, 1, NKE_RECORD_END_OF_MESSAGE, NULL, 0))
    return 0;

  inst->message.complete = 1;

  return 1;
}

/* ================================================== */

int
NKSN_GetRecord(NKSN_Instance inst, int *critical, int *type, int *body_length,
               void *body, int buffer_length)
{
  int type2;

  assert(inst->message.complete);

  if (body_length)
    *body_length = 0;

  if (!get_record(&inst->message, critical, &type2, body_length, body, buffer_length))
    return 0;

  /* Hide the end-of-message record */
  if (type2 == NKE_RECORD_END_OF_MESSAGE)
    return 0;

  if (type)
    *type = type2;

  return 1;
}

/* ================================================== */

int
NKSN_GetKeys(NKSN_Instance inst, SIV_Algorithm siv, NKE_Key *c2s, NKE_Key *s2c)
{
  int length = SIV_GetKeyLength(siv);

  if (length <= 0 || length > sizeof (c2s->key) || length > sizeof (s2c->key)) {
    DEBUG_LOG("Invalid algorithm");
    return 0;
  }

  if (gnutls_prf_rfc5705(inst->tls_session,
                         sizeof (NKE_EXPORTER_LABEL) - 1, NKE_EXPORTER_LABEL,
                         sizeof (NKE_EXPORTER_CONTEXT_C2S) - 1, NKE_EXPORTER_CONTEXT_C2S,
                         length, (char *)c2s->key) < 0 ||
      gnutls_prf_rfc5705(inst->tls_session,
                         sizeof (NKE_EXPORTER_LABEL) - 1, NKE_EXPORTER_LABEL,
                         sizeof (NKE_EXPORTER_CONTEXT_S2C) - 1, NKE_EXPORTER_CONTEXT_S2C,
                         length, (char *)s2c->key) < 0) {
    DEBUG_LOG("Could not export key");
    return 0;
  }

  c2s->length = length;
  s2c->length = length;

  return 1;
}

/* ================================================== */

int
NKSN_IsStopped(NKSN_Instance inst)
{
  return inst->state == KE_STOPPED;
}

/* ================================================== */

void
NKSN_StopSession(NKSN_Instance inst)
{
  stop_session(inst);
}

/* ================================================== */

int
NKSN_GetRetryFactor(NKSN_Instance inst)
{
  return inst->retry_factor;
}
