/* Copyright 2022-2026 Kurt Nienhaus
 *
 * This file is part of VgaGames.
 * VgaGames is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 * VgaGames 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 VgaGames.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdarg.h>
#include <syslog.h>
#include "nw.h"
#ifdef SML3_HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif

void init_nw_server(void);

#define MAX_PKSENT 8

enum { SV_NOP = -1, SV_OFF = 0, SV_ON = 1, SV_OFFON = 2 };
static const char lockfile[] = "/tmp/VG4_nw_server.lock";
static const int max_memsize_pkgarray = 16777215;


struct udp_server {      /* UDP-server-struct */
  struct {                 /* multicast-/broadcast-server */
    int cpid4, cpid6;        /* child-PIDs for ipv4 and ipv6 */
    int fd4, fd6;            /* descriptors for ipv4 and ipv6 */
  } mbcast;
  sigset_t sa_termmask;    /* blocked SIGTERM */
  int ipv;                 /* 4 or 6 */
  int sockfd;              /* non-blocking UDP-socket-descriptor */
  socklen_t salen;         /* length for the remote-address */
  struct sockaddr *sad;    /* for receiving from remote-address */
  unsigned char conncl;    /* bitwise which clients are connected */
  int maxconn;             /* number of UDP-connections */
  struct {                 /* UDP-connections */
    VG_BOOL active;          /* whether connection is active */
    unsigned short seqnr;    /* last sequence-number of client */
    socklen_t salen;         /* length of the remote-address */
    struct sockaddr *sad;    /* remote-address */
    char hname[MAX_ZDATA / MAX_CLIENTS - 1];  /* client's hostname */
    unsigned int pksent[MAX_PKSENT];  /* statistic of sent packages */
    struct nwdata_zdata *zdata;  /* actual receiving additional-data */
    struct {                 /* exchange-data to be sent actually */
      unsigned short seqnr;    /* last sequence-number of client */
      struct nwdata_xdata *data_recv;  /* actual receiving exchange-data */
      struct nwdata_xdata *data_send;  /* pointer to exchange-data list-element */
      int posv, posb;          /* position in data_send: from, to (excl.) */
    } xdata;
  } *conn;
  struct {                 /* keys-data */
    int max;                 /* allocated elements of data */
    int pos;                 /* current element of data to fill */
    struct nwdata_run_keys_s2c *data;  /* data */
  } nwkeys;
  struct nwdata_zdata *zdata_list;  /* additional-data list to be sent */
  struct nwdata_xdata *xdata_list;  /* exchange-data list to be sent */
};

void sv_outlog(int, const char *, ...);
VG_BOOL nw_sv_runlocal(void);

static void nw_server(const char *);

static void do_term(int);
static int sv_repl(void *, const char *, char *, size_t *);
static void mbcast_start(struct udp_server *, const char *);
static void mbcast_stop(struct udp_server *);
static int optfunk(int);
static VG_BOOL get_lock(int);
static VG_BOOL no_more_conn(struct udp_server *);
static void send_close(struct udp_server *, int);
static VG_BOOL send2client(struct udp_server *, struct nwdata_packets *, unsigned char *, int);
static VG_BOOL send_empty_xdata(struct udp_server *, struct nwdata_packets *, unsigned char *, int);
static VG_BOOL get_connections(struct udp_server *);
static VG_BOOL run_connections(struct udp_server *);


/* set functions */
void
init_nw_server(void)
{
  vg4->nw->server = nw_server;
} /* Ende init_nw_server */


/* syslog- or terminal-output */
void
sv_outlog(int priority, const char *format, ...)
{
  va_list ap;

  va_start(ap, format);

  if (isatty(STDERR_FILENO)) {
    char *level = NULL;
    switch (priority) {
      case LOG_EMERG:   level = "EMERG"; break;
      case LOG_ALERT:   level = "ALERT"; break;
      case LOG_CRIT:    level = "CRIT"; break;
      case LOG_ERR:     level = "ERR"; break;
      case LOG_WARNING: level = "WARN"; break;
      case LOG_NOTICE:  level = "NOTICE"; break;
      case LOG_INFO:    level = "INFO"; break;
      case LOG_DEBUG:   level = "DEBUG"; break;
      default: level = "?";
    }
    fprintf(stderr, "[%s] ", level);
    vfprintf(stderr, format, ap);
    fprintf(stderr, "\n");
    fflush(stderr);
  } else if (priority != LOG_DEBUG) {
    vsyslog(priority, format, ap);
  }
  va_end(ap);
} /* Ende sv_outlog */


/* check whether nw-server is running locally */
VG_BOOL
nw_sv_runlocal(void)
{
  if (get_lock(SV_NOP)) { return VG_TRUE; }
  return VG_FALSE;
} /* Ende nw_sv_runlocal */


/* handler for SIGTERM */
static void
do_term(int signum)
{
  (void)signum;
} /* Ende do_term */


/* +++++++++++++++++++++++++++ */
/* multicast-/broadcast-server */
/* +++++++++++++++++++++++++++ */

/* reply function for multicast-/broadcast-server, replies hostname */
static int
sv_repl(void *vptr, const char *ipnr, char *daten, size_t *len)
{
  char buf[SML3_NW_MBCAST_MAXDATA];

  if (gethostname(buf, sizeof(buf)) < 0) { snprintf(buf, sizeof(buf), "<nobody>"); }
  *len = strlen(buf);
  memmove(daten, buf, *len);

  (void)vptr;
  (void)ipnr;
  return 1;
} /* Ende sv_repl */


/* start broadcast-/multicast-server */
static void
mbcast_start(struct udp_server *udpsv, const char *mbport)
{
  if (udpsv == NULL) { return; }

  udpsv->mbcast.cpid4 = udpsv->mbcast.cpid6 = 0;
  udpsv->mbcast.fd4 = udpsv->mbcast.fd6 = -1;

  /* start for ipv4 and if possible for ipv6 */

  udpsv->mbcast.cpid4 = SML3_nw_mbcast_server(mbport, 4, &udpsv->mbcast.fd4, sv_repl, NULL);
  if (udpsv->mbcast.cpid4 < 0) { outlog(LOG_ERR, "Start MBCast(ipv4): %s", SML3_fehlermsg()); }

#if defined(AF_INET6) && defined(IPV6_V6ONLY)
  udpsv->mbcast.cpid6 = SML3_nw_mbcast_server(mbport, 6, &udpsv->mbcast.fd6, sv_repl, NULL);
  if (udpsv->mbcast.cpid4 < 0) { outlog(LOG_ERR, "Start MBCast(ipv6): %s", SML3_fehlermsg()); }
#endif
} /* Ende mbcast_start */


/* stop broadcast-/multicast-server */
static void
mbcast_stop(struct udp_server *udpsv)
{
  if (udpsv == NULL) { return; }

  if (udpsv->mbcast.fd4 >= 0) { close(udpsv->mbcast.fd4); udpsv->mbcast.fd4 = -1; }
  if (udpsv->mbcast.fd6 >= 0) { close(udpsv->mbcast.fd6); udpsv->mbcast.fd6 = -1; }
  SML3_sleep_msek(100);
  if (udpsv->mbcast.cpid4 > 0) {
    waitpid(udpsv->mbcast.cpid4, NULL, WNOHANG);
    udpsv->mbcast.cpid4 = 0;
  }
  if (udpsv->mbcast.cpid6 > 0) {
    waitpid(udpsv->mbcast.cpid6, NULL, WNOHANG);
    udpsv->mbcast.cpid6 = 0;
  }
} /* Ende mbcast_stop */


/* ++++++++++++++ */
/* network-server */
/* ++++++++++++++ */

/* IPv6-Hybridmodus einschalten, um IPv4 und IPv6 empfangen zu koennen */
static int
optfunk(int sockfd)
{
#if defined(AF_INET6) && defined(IPV6_V6ONLY)
  int on = 0;
  setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
#endif
  return 0;
} /* Ende optfunk */


/* operate on lockfile according to iaction
 * return VG_TRUE = OK or VG_FALSE = error
 */
static VG_BOOL
get_lock(int iaction)
{
  int fdl;
  pid_t lockpid;

lock_reopen:
  fdl = open(lockfile, O_RDWR);
  if (fdl < 0) {
    if (iaction == SV_NOP) { return VG_FALSE; }
    if (iaction == SV_OFF) { return VG_TRUE; }
    fdl = open(lockfile, O_RDWR | O_CREAT, (mode_t)0666);
    if (fdl < 0) { outlog(LOG_ERR, "open lockfile %s: %s", lockfile, strerror(errno)); return VG_FALSE; }
  }

  if (!SML3_getlock(fdl, 0L, 1L, &lockpid, 1)) {
    if (SML3_fehlererrno() == ENOLCK) {
      if (iaction == SV_NOP) { close(fdl); return VG_TRUE; }
      if (iaction == SV_ON) {
        outlog(LOG_WARNING, "Lockfile %s is locked by PID %d, not killing", lockfile, (int)lockpid);
        close(fdl);
        return VG_FALSE;
      }
      outlog(LOG_WARNING, "Lockfile %s is locked by PID %d, killing process", lockfile, (int)lockpid);
      kill(lockpid, SIGTERM);
      sleep(2);
      if (!SML3_getlock(fdl, 0L, 1L, &lockpid, 1)) {
        if (SML3_fehlererrno() == ENOLCK) {
          outlog(LOG_ERR, "Lockfile %s is still locked by PID %d", lockfile, (int)lockpid);
        } else {
          outlog(LOG_ERR, "locking lockfile %s: %s", lockfile, SML3_fehlermsg());
        }
        close(fdl);
        return VG_FALSE;
      }
    } else {
      outlog(LOG_ERR, "locking lockfile %s: %s", lockfile, SML3_fehlermsg());
      close(fdl);
      return VG_FALSE;
    }
    if (iaction == SV_OFFON) { close(fdl); goto lock_reopen; }
  }

  if (iaction == SV_NOP) { close(fdl); return VG_FALSE; }
  if (iaction == SV_OFF) { close(fdl); unlink(lockfile); }
  return VG_TRUE;
} /* Ende get_lock */


/* check if all connections are gone
 * return: VG_TRUE = gone, VG_FALSE = at least one still active
 */
static VG_BOOL
no_more_conn(struct udp_server *udpsv)
{
  int iconn;

  if (udpsv == NULL) { return VG_FALSE; }

  for (iconn = 0; iconn < udpsv->maxconn; iconn++) {
    if (udpsv->conn[iconn].active) { break; }
  }
  if (iconn == udpsv->maxconn) { return VG_TRUE; }

  return VG_FALSE;
} /* Ende no_more_conn */


/* send close-packet to client */
static void
send_close(struct udp_server *udpsv, int iconn)
{
  struct nwdata_packets nwpack;
  unsigned char dgram[NW_MAXPAKET];
  VG_BOOL active;

  if (iconn < 0 || iconn >= udpsv->maxconn) { return; }
  memset(&nwpack, 0, sizeof(nwpack));
  nwpack.kenner = NWDATA_KENNER_RUNCONN;
  nwpack.action = NWDATA_ACTION_CLOSE;
  active = udpsv->conn[iconn].active;
  udpsv->conn[iconn].active = VG_TRUE;
  send2client(udpsv, &nwpack, dgram, iconn);
  udpsv->conn[iconn].active = active;
} /* Ende send_close */


/* send data in nwpack via dgram to client-index iconn or all clients */
static VG_BOOL
send2client(struct udp_server *udpsv, struct nwdata_packets *nwpack, unsigned char *dgram, int iconn)
{
  size_t slen;
  VG_BOOL retw = VG_TRUE;
#ifdef NWPACK_DUMP
  char bhost[128], remaddr[144];
  int bport;
#endif

  if (udpsv == NULL || nwpack == NULL || dgram == NULL) { return VG_TRUE; }

  fcntl(udpsv->sockfd, F_SETFL, fcntl(udpsv->sockfd, F_GETFL, 0) & ~O_NONBLOCK);

  if (iconn >= 0 && iconn < udpsv->maxconn) {  /* to client[iconn] */
    if (udpsv->conn[iconn].active) {  /* used */
#ifdef NWPACK_DUMP
      if (SML3_nw_udp_peername(udpsv->sockfd, udpsv->conn[iconn].sad, udpsv->conn[iconn].salen, bhost, sizeof(bhost), &bport) == 0) {
        snprintf(remaddr, sizeof(remaddr), "%s:%d", bhost, bport);
      } else {
        snprintf(remaddr, sizeof(remaddr), "(no-addr)");
      }
      slen = sv_nwpack2dgram(remaddr, nwpack, dgram, NW_ICONN2CLNR(iconn));
#else
      slen = sv_nwpack2dgram(NULL, nwpack, dgram, NW_ICONN2CLNR(iconn));
#endif
      if (slen > 0) {
        if (sendto(udpsv->sockfd, dgram, slen, 0, udpsv->conn[iconn].sad, udpsv->conn[iconn].salen) < 0) {
          outlog(LOG_WARNING, "Sending to client %d [%s] failed: %s", NW_ICONN2CLNR(iconn), udpsv->conn[iconn].hname, strerror(errno));
          retw = VG_FALSE;
        }
      }
    }
  } else if (iconn < 0) {  /* to all clients */
    int iret = -1;
    for (iconn = 0; iconn < udpsv->maxconn; iconn++) {
      if (!udpsv->conn[iconn].active) { continue; }  /* unused */
#ifdef NWPACK_DUMP
      if (SML3_nw_udp_peername(udpsv->sockfd, udpsv->conn[iconn].sad, udpsv->conn[iconn].salen, bhost, sizeof(bhost), &bport) == 0) {
        snprintf(remaddr, sizeof(remaddr), "%s:%d", bhost, bport);
      } else {
        snprintf(remaddr, sizeof(remaddr), "(no-addr)");
      }
      slen = sv_nwpack2dgram(remaddr, nwpack, dgram, NW_ICONN2CLNR(iconn));
#else
      slen = sv_nwpack2dgram(NULL, nwpack, dgram, NW_ICONN2CLNR(iconn));
#endif
      if (slen > 0) {
        if (sendto(udpsv->sockfd, dgram, slen, 0, udpsv->conn[iconn].sad, udpsv->conn[iconn].salen) < 0) {
          outlog(LOG_WARNING, "Sending to client %d [%s] failed: %s", NW_ICONN2CLNR(iconn), udpsv->conn[iconn].hname, strerror(errno));
          if (iret < 0) { iret = 0; }
        } else {
          iret = 1;
        }
      }
    }
    if (iret == 0) { retw = VG_FALSE; }
  }

  fcntl(udpsv->sockfd, F_SETFL, fcntl(udpsv->sockfd, F_GETFL, 0) | O_NONBLOCK);

  return retw;
} /* Ende send2client */


/* send xdata to client without data */
static VG_BOOL
send_empty_xdata(struct udp_server *udpsv, struct nwdata_packets *nwpack, unsigned char *dgram, int iconn)
{
  struct nwdata_xdata xdummy;
  int xlen;

  if (udpsv == NULL || nwpack == NULL || dgram == NULL) { return VG_TRUE; }

  memset(&xdummy, 0, sizeof(xdummy));
  xdummy.conncl = udpsv->conncl;
  xlen = nw_xheader_set(&xdummy, nwpack->u.k_runconn.a_xdata_send.s2c.xdata);
  nwpack->u.k_runconn.a_xdata_send.s2c.xdatalen = xlen;

  return send2client(udpsv, nwpack, dgram, iconn);
} /* Ende send_empty_xdata */


/* nw_server:
 * start, stop or restart network-server
 * @param action  "stop", "start", "restart", "status"
 */
static void
nw_server(const char *action)
{
  struct vgi_nw *sptr;
  long imax;
  struct udp_server udpsv;
  struct sigaction sac;
  VG_BOOL bret;
  char svportb[32], mbportb[32];
  int iaction;

  if (action == NULL) { action = ""; }
  if (strcmp(action, "stop") == 0) {
    iaction = SV_OFF;
  } else if (strcmp(action, "start") == 0) {
    iaction = SV_ON;
  } else if (strcmp(action, "restart") == 0) {
    iaction = SV_OFFON;
  } else if (strcmp(action, "status") == 0) {
    iaction = SV_NOP;
  } else {
    outlog(LOG_ERR, "network-server: invalid action: \"%s\"", action);
    return;
  }

  sptr = vg4data.lists.nw.s;

  snprintf(svportb, sizeof(svportb), "%d", sptr->svport);
  snprintf(mbportb, sizeof(mbportb), "%d", sptr->mbport);

  if (iaction == SV_NOP) {
    if (nw_sv_runlocal()) {
      printf("Running\n");
    } else {
      printf("Stopped\n");
    }
    return;
  }
  if (iaction == SV_OFF) { get_lock(iaction); return; }

  memset(&udpsv, 0, sizeof(udpsv));

#ifndef NWPACK_DUMP
  { pid_t cpid = fork();
    if (cpid == (pid_t)-1) {
      outlog(LOG_ERR, "Starting network-server: fork: %s", strerror(errno));
      return;
    }
    if (cpid != 0) {  /* parent */
      fprintf(stderr, "Starting network-server at UDP-Port %s (MBCast at UDP-Port %s) ...\n", svportb, mbportb);
      sleep(3);  /* wait while network-server starts */
      return;
    }
  }
#endif

  /* close descriptors */
  imax = sysconf(_SC_OPEN_MAX);
  if (imax > 9999L || imax < 0) { imax = 9999L; }
  while (--imax >= 0) {
    if ((int)imax == STDIN_FILENO) { continue; }
    if ((int)imax == STDOUT_FILENO) { continue; }
    if ((int)imax == STDERR_FILENO) { continue; }
    close((int)imax);
  }

#ifndef NWPACK_DUMP
  SML3_daemon(0, 0);
  openlog("VG4-network-server", LOG_PID, LOG_DAEMON);
#endif

  /* get lockfile */
  if (!get_lock(iaction)) { _exit(1); }

  /* set SIGTERM to default */
  memset(&sac, 0, sizeof(sac));
  sac.sa_handler = SIG_DFL;
  sigaction(SIGTERM, &sac, NULL);

  /* create server-UDP-socket */
#if defined(AF_INET6) && defined(IPV6_V6ONLY)
  udpsv.ipv = 6;
#else
  udpsv.ipv = 4;
#endif
  udpsv.sockfd = SML3_nw_udp_server(NULL, svportb, &udpsv.ipv, &udpsv.salen, optfunk);
  if (udpsv.sockfd < 0) {
    outlog(LOG_ERR, "%s", SML3_fehlermsg());
    unlink(lockfile);
    _exit(1);
  }
  fcntl(udpsv.sockfd, F_SETFL, fcntl(udpsv.sockfd, F_GETFL, 0) | O_NONBLOCK);
  udpsv.sad = SML3_malloc(udpsv.salen);

  /* ignore SIGPIPE */
  memset(&sac, 0, sizeof(sac));
  sac.sa_handler = SIG_IGN;
  sigaction(SIGPIPE, &sac, NULL);

  /* start multicast-/broadcast-server */
  mbcast_start(&udpsv, mbportb);

  /* set SIGTERM to handler and block it */
  sigemptyset(&udpsv.sa_termmask);
  sigaddset(&udpsv.sa_termmask, SIGTERM);
  sigprocmask(SIG_BLOCK, &udpsv.sa_termmask, NULL);
  memset(&sac, 0, sizeof(sac));
  sac.sa_handler = do_term;
  sigaction(SIGTERM, &sac, NULL);

  /* allocate packet-array */
  udpsv.nwkeys.max = max_memsize_pkgarray / (int)sizeof(struct nwdata_run_keys_s2c);
  if (udpsv.nwkeys.max < 2) {
    outlog(LOG_ERR, "error: array-size < 2: %d\n", udpsv.nwkeys.max);
    unlink(lockfile);
    _exit(1);
  }
  if (udpsv.nwkeys.max > (int)(unsigned short)-1) { udpsv.nwkeys.max = (int)(unsigned short)-1; }
  udpsv.nwkeys.data = SML3_malloc(sizeof(struct nwdata_run_keys_s2c) * udpsv.nwkeys.max);

#ifndef NWPACK_DUMP
  outlog(LOG_NOTICE, "Server started at UDP-Port %s with MBCast at UDP-Port %s", svportb, mbportb);
#else
  outlog(LOG_NOTICE, "Server started at UDP-Port %s with MBCast at UDP-Port %s - dump modus (foreground)", svportb, mbportb);
#endif

  for (;;) {
    /* wait for connections */
    outlog(LOG_WARNING, "Switching to status \"Waiting for connections\"");
    if (!get_connections(&udpsv)) { break; }

    /* run connections */
    bret = VG_TRUE;
    if (udpsv.maxconn > 0) {
      outlog(LOG_WARNING, "Switching to status \"Running connections\"");
      bret = run_connections(&udpsv);
    }

    if (udpsv.conn != NULL) {
      int iconn, i1;
      struct SML3_gummi gm = SML3_GUM_INITIALIZER;
      size_t gmpos;
      unsigned SML3_int64 pkges;
      for (iconn = 0; iconn < udpsv.maxconn; iconn++) {
        if (udpsv.conn[iconn].sad != NULL) { free(udpsv.conn[iconn].sad); }
        for (pkges = 0, i1 = 0; i1 < MAX_PKSENT; i1++) { pkges += udpsv.conn[iconn].pksent[i1]; }
        if (pkges > 0) {
          gmpos = 0;
          for (i1 = 0; i1 < MAX_PKSENT; i1++) {
            gmpos += SML3_gumprintf(&gm, gmpos, " [%d%s]=%u%%", i1 + 1, (i1 == MAX_PKSENT - 1 ? "+" : ""), udpsv.conn[iconn].pksent[i1] * 100 / pkges);
          }
          outlog(LOG_NOTICE, "Sent-packet-per-request-statistic for client %d [%s]:%s", NW_ICONN2CLNR(iconn), udpsv.conn[iconn].hname, SML3_gumgetval(&gm));
        }
        memset(udpsv.conn[iconn].pksent, 0, sizeof(udpsv.conn[iconn].pksent));
        nw_free_nwdata_zdata(udpsv.conn[iconn].zdata);
        nw_free_nwdata_xdata(udpsv.conn[iconn].xdata.data_recv);
        udpsv.conn[iconn].xdata.data_send = NULL;
      }
      SML3_gumdest(&gm);
      free(udpsv.conn);
      udpsv.conn = NULL;
    }
    udpsv.maxconn = 0;
    udpsv.conncl = 0;
    nw_free_nwdata_zdata(udpsv.zdata_list);
    udpsv.zdata_list = NULL;
    nw_free_nwdata_xdata(udpsv.xdata_list);
    udpsv.xdata_list = NULL;
    if (!bret) { break; }
  }

  /* stop multicast-/broadcast-server */
  mbcast_stop(&udpsv);

  close(udpsv.sockfd);
  free(udpsv.sad);
  free(udpsv.nwkeys.data);
  outlog(LOG_WARNING, "Server stopped");
#ifndef NWPACK_DUMP
  closelog();
#endif
  unlink(lockfile);
  _exit(0);
} /* Ende nw_server */


/* get connections */
static VG_BOOL
get_connections(struct udp_server *udpsv)
{
  const int timo = 120;  /* timeout in seconds */
  unsigned int nw_seed;
  int maxconn, iconn, i1, erg, hlen, bport;
  unsigned char dgram[NW_MAXPAKET];
  char bhost[128], hname[MAX_ZDATA], *pt1;
  ssize_t dglen;
  size_t slen;
  struct timeval tv;
  fd_set fdread;
  struct nwdata_packets nwpack;
#ifdef NWPACK_DUMP
  char remaddr[144];
#endif

  if (udpsv == NULL) { outlog(LOG_ERR, "get_connections: parameter invalid"); return VG_FALSE; }

  nw_seed = SML3_zufallzahl(1, 2147483647);
  *hname = '\0';
  hlen = 0;

  udpsv->conn = SML3_malloc(sizeof(*udpsv->conn));
  udpsv->maxconn = 0;

  maxconn = 0;
  for (;;) {
    FD_ZERO(&fdread);
    FD_SET(udpsv->sockfd, &fdread);
    tv.tv_sec = timo;
    tv.tv_usec = 0;

    /* wait for socket action */
    sigprocmask(SIG_UNBLOCK, &udpsv->sa_termmask, NULL);
    erg = select(udpsv->sockfd + 1, &fdread, NULL, NULL, &tv);
    sigprocmask(SIG_BLOCK, &udpsv->sa_termmask, NULL);
    if (erg < 0) {
      int prio;
      if (errno == EINTR) { prio = LOG_WARNING; } else { prio = LOG_ERR; }
      outlog(prio, "Waiting for connections: select: %s", strerror(errno));
      return VG_FALSE;
    }
    if (erg == 0) {
      outlog(LOG_WARNING, "Waiting for connections: timeout");
      break;
    }

    if (FD_ISSET(udpsv->sockfd, &fdread)) {  /* nw-packet available */
      socklen_t satmp = udpsv->salen;

      /* receive nw-packet */
      dglen = recvfrom(udpsv->sockfd, dgram, sizeof(dgram), 0, udpsv->sad, &satmp);
      if (dglen == 0) { continue; }
      if (dglen < 0) {
        if (SML3_errno_is_eagain(errno)) { continue; }
        outlog(LOG_ERR, "Waiting for connections: recvfrom: %s", strerror(errno));
        return VG_FALSE;
      }
      slen = (size_t)dglen;

      /* set nw-packet into struct */
#ifdef NWPACK_DUMP
      if (SML3_nw_udp_peername(udpsv->sockfd, udpsv->sad, satmp, bhost, sizeof(bhost), &bport) == 0) {
        snprintf(remaddr, sizeof(remaddr), "%s:%d", bhost, bport);
      } else {
        snprintf(remaddr, sizeof(remaddr), "(no-addr)");
      }
      sv_dgram2nwpack(remaddr, dgram, slen, &nwpack);
#else
      sv_dgram2nwpack(NULL, dgram, slen, &nwpack);
#endif

      /* client closed connection? */
      if (nwpack.kenner == NWDATA_KENNER_RUNCONN && nwpack.action == NWDATA_ACTION_CLOSE) {  /* close connection */
        for (iconn = 0; iconn < udpsv->maxconn; iconn++) {
          if (satmp == udpsv->conn[iconn].salen && nw_addr_equal(udpsv->ipv, udpsv->sad, udpsv->conn[iconn].sad, satmp)) {
            /* actualize hostnames */
            { int hpos1 = 0, hpos2;
              if (iconn > 0) {
                int nl = 0;
                for (; hpos1 < hlen; hpos1++) {
                  if (hname[hpos1] == '\n') {
                    if (++nl == iconn) { hpos1++; break; }
                  }
                }
              }
              for (hpos2 = hpos1; hpos2 < hlen; hpos2++) {
                if (hname[hpos2] == '\n') { hpos2++; break; }
              }
              if (hpos2 < hlen) { memmove(&hname[hpos1], &hname[hpos2], hlen - hpos2); }
              hlen = hpos1 + (hlen - hpos2);
            }

            /* actualize connections */
            free(udpsv->conn[iconn].sad);
            if (iconn < udpsv->maxconn - 1) {
              memmove(&udpsv->conn[iconn], &udpsv->conn[iconn + 1], sizeof(udpsv->conn[0]) * (udpsv->maxconn - iconn - 1));
            }
            nw_conncl_set(&udpsv->conncl, NW_ICONN2CLNR(udpsv->maxconn - 1), 0);
            udpsv->maxconn--;
            outlog(LOG_NOTICE, "Client %d [%s] closed connection", NW_ICONN2CLNR(iconn), udpsv->conn[iconn].hname);

            /* send actualized connection-info to all clients */
            memset(&nwpack, 0, sizeof(nwpack));
            nwpack.kenner = NWDATA_KENNER_GETCONN;
            nwpack.action = NWDATA_ACTION_NEWCONN;
            nwpack.u.k_getconn.a_newconn.s2c.hnamelen = hlen;
            memcpy(nwpack.u.k_getconn.a_newconn.s2c.hname, hname, hlen);
            send2client(udpsv, &nwpack, dgram, -1);
            break;
          }
        }
        continue;
      }

      if (nwpack.kenner != NWDATA_KENNER_GETCONN) { continue; }

      /* process request */

      if (nwpack.action == NWDATA_ACTION_NEWCONN) {  /* new connection */
        /* don't connect twice */
        for (iconn = 0; iconn < udpsv->maxconn; iconn++) {
          if (satmp == udpsv->conn[iconn].salen && nw_addr_equal(udpsv->ipv, udpsv->sad, udpsv->conn[iconn].sad, satmp)) {
            if (SML3_nw_udp_peername(udpsv->sockfd, udpsv->sad, satmp, bhost, sizeof(bhost), &bport) == 0) {
              outlog(LOG_WARNING, "Waiting for connections: repeated connection refused from %s:%d", bhost, bport);
            } else {
              outlog(LOG_WARNING, "Waiting for connections: repeated connection refused");
            }
            break;
          }
        }
        if (iconn < udpsv->maxconn) {
          /* send connection-info again to client */
          memset(&nwpack, 0, sizeof(nwpack));
          nwpack.kenner = NWDATA_KENNER_GETCONN;
          nwpack.action = NWDATA_ACTION_NEWCONN;
          nwpack.u.k_getconn.a_newconn.s2c.hnamelen = hlen;
          memcpy(nwpack.u.k_getconn.a_newconn.s2c.hname, hname, hlen);
          send2client(udpsv, &nwpack, dgram, iconn);
          continue;
        }

        /* add connection */
        udpsv->maxconn++;
        udpsv->conn = SML3_realloc(udpsv->conn, udpsv->maxconn * sizeof(*udpsv->conn));
        udpsv->conn[udpsv->maxconn - 1].active = VG_TRUE;
        udpsv->conn[udpsv->maxconn - 1].seqnr = (unsigned short)-1;
        udpsv->conn[udpsv->maxconn - 1].sad = SML3_malloc(satmp);
        memmove(udpsv->conn[udpsv->maxconn - 1].sad, udpsv->sad, satmp);
        udpsv->conn[udpsv->maxconn - 1].salen = satmp;
        memset(udpsv->conn[udpsv->maxconn - 1].pksent, 0, sizeof(udpsv->conn[udpsv->maxconn - 1].pksent));
        *udpsv->conn[udpsv->maxconn - 1].hname = '\0';
        udpsv->conn[udpsv->maxconn - 1].zdata = NULL;
        udpsv->conn[udpsv->maxconn - 1].xdata.seqnr = 0;
        udpsv->conn[udpsv->maxconn - 1].xdata.data_recv = NULL;
        udpsv->conn[udpsv->maxconn - 1].xdata.data_send = NULL;
        udpsv->conn[udpsv->maxconn - 1].xdata.posv = udpsv->conn[udpsv->maxconn - 1].xdata.posb = 0;
        nw_conncl_set(&udpsv->conncl, NW_ICONN2CLNR(udpsv->maxconn - 1), 1);

        if (maxconn == 0) {
          if (nwpack.u.k_getconn.a_newconn.c2s.maxcl > 0 && nwpack.u.k_getconn.a_newconn.c2s.maxcl <= MAX_CLIENTS) {
            /* limit number of clients */
            maxconn = nwpack.u.k_getconn.a_newconn.c2s.maxcl;
            if (maxconn < udpsv->maxconn) { maxconn = udpsv->maxconn; }
          }
        }

        /* add hostname to hostnames */
        pt1 = hname + hlen;
        i1 = (int)strlen(nwpack.u.k_getconn.a_newconn.c2s.hname);
        if (i1 > 0) {
          memcpy(hname + hlen, nwpack.u.k_getconn.a_newconn.c2s.hname, i1);
          hlen += i1;
        } else {
          hname[hlen++] = '?';
          i1 = 1;
        }
        hname[hlen++] = '\n';
        snprintf(udpsv->conn[udpsv->maxconn - 1].hname, sizeof(udpsv->conn[udpsv->maxconn - 1].hname), "%.*s", i1, pt1);

        /* log new connection */
        if (SML3_nw_udp_peername(udpsv->sockfd,
                                 udpsv->conn[udpsv->maxconn - 1].sad,
                                 udpsv->conn[udpsv->maxconn - 1].salen,
                                 bhost, sizeof(bhost), &bport) == 0) {
          outlog(LOG_NOTICE, "Waiting for connections: new connection from %s:%d [%s]", bhost, bport, udpsv->conn[udpsv->maxconn - 1].hname);
        } else {
          outlog(LOG_NOTICE, "Waiting for connections: new connection [%s]", udpsv->conn[udpsv->maxconn - 1].hname);
        }

        /* send new connection-info to all clients */
        memset(&nwpack, 0, sizeof(nwpack));
        nwpack.kenner = NWDATA_KENNER_GETCONN;
        nwpack.action = NWDATA_ACTION_NEWCONN;
        nwpack.u.k_getconn.a_newconn.s2c.hnamelen = hlen;
        memcpy(nwpack.u.k_getconn.a_newconn.s2c.hname, hname, hlen);
        send2client(udpsv, &nwpack, dgram, -1);

        /* check for limit of clients */
        if (udpsv->maxconn == MAX_CLIENTS || (maxconn > 0 && udpsv->maxconn == maxconn)) {
          outlog(LOG_WARNING, "Waiting for connections: limit of clients reached: %d", udpsv->maxconn);
          break;
        }

      } else if (nwpack.action == NWDATA_ACTION_ENDCONN) {  /* end getting connections */
        /* check if request comes from a connected client */
        for (iconn = 0; iconn < udpsv->maxconn; iconn++) {
          if (satmp == udpsv->conn[iconn].salen && nw_addr_equal(udpsv->ipv, udpsv->sad, udpsv->conn[iconn].sad, satmp)) {
            if (SML3_nw_udp_peername(udpsv->sockfd, udpsv->sad, satmp, bhost, sizeof(bhost), &bport) == 0) {
              outlog(LOG_NOTICE, "Waiting for connections: status-quit-request from %s:%d", bhost, bport);
            } else {
              outlog(LOG_NOTICE, "Waiting for connections: status-quit-request");
            }
            break;
          }
        }
        if (iconn < udpsv->maxconn) { break; }
      }
    }
  }

  /* send final reply to all clients */
  memset(&nwpack, 0, sizeof(nwpack));
  nwpack.kenner = NWDATA_KENNER_GETCONN;
  nwpack.action = NWDATA_ACTION_ENDCONN;
  nwpack.u.k_getconn.a_endconn.s2c.clnr = 0;  /* will be set later */
  pt1 = SML3_uitostr(nw_seed, 36, NULL, 0);
  slen = strlen(pt1);
  if (slen > sizeof(nwpack.u.k_getconn.a_endconn.s2c.nw_seed)) { slen = sizeof(nwpack.u.k_getconn.a_endconn.s2c.nw_seed); }
  memcpy(nwpack.u.k_getconn.a_endconn.s2c.nw_seed, pt1, slen);
  free(pt1);
  nwpack.u.k_getconn.a_endconn.s2c.hnamelen = hlen;
  memcpy(nwpack.u.k_getconn.a_endconn.s2c.hname, hname, hlen);
  for (i1 = 0; i1 < 3; i1++) { send2client(udpsv, &nwpack, dgram, -1); }  /* send more than one, in case a packet is lost */

  outlog(LOG_NOTICE, "Waiting for connections: %d clients finally connected", udpsv->maxconn);

  return VG_TRUE;
} /* Ende get_connections */


/* handle connections */
static VG_BOOL
run_connections(struct udp_server *udpsv)
{
  const int timo = 60;  /* timeout in seconds */
  struct timeval tv;
  fd_set fdread;
  unsigned char dgram[NW_MAXPAKET];
  struct nwdata_packets nwpack;
  socklen_t satmp;
  ssize_t dglen;
  size_t slen;
  int erg, iconn, ilen;
  unsigned short seqnr_fill;
  VG_BOOL pause;
#ifdef NWPACK_DUMP
  char bhost[128], remaddr[144];
  int bport;
#endif

  if (udpsv == NULL) { outlog(LOG_ERR, "run_connections: parameter invalid"); return VG_FALSE; }

  /* initialize keys-data-array with sequence-numbers */
  seqnr_fill = 0;
  for (udpsv->nwkeys.pos = 0; udpsv->nwkeys.pos < udpsv->nwkeys.max; udpsv->nwkeys.pos++) {
    udpsv->nwkeys.data[udpsv->nwkeys.pos].seqnr = seqnr_fill++;
  }

  /* set keys-data-element to fill */
  seqnr_fill = 1;
  memset(&udpsv->nwkeys.data[0], 0, sizeof(*udpsv->nwkeys.data));
  udpsv->nwkeys.data[0].seqnr = 0;
  udpsv->nwkeys.data[0].conncl = udpsv->conncl;
  memset(&udpsv->nwkeys.data[1], 0, sizeof(*udpsv->nwkeys.data));
  udpsv->nwkeys.data[1].seqnr = seqnr_fill;
  udpsv->nwkeys.data[1].conncl = udpsv->conncl;
  udpsv->nwkeys.pos = 1;

  pause = VG_FALSE;
  for (;;) {
    FD_ZERO(&fdread);
    FD_SET(udpsv->sockfd, &fdread);
    tv.tv_sec = timo;
    tv.tv_usec = 0;

    /* wait for socket action */
    sigprocmask(SIG_UNBLOCK, &udpsv->sa_termmask, NULL);
    erg = select(udpsv->sockfd + 1, &fdread, NULL, NULL, &tv);
    sigprocmask(SIG_BLOCK, &udpsv->sa_termmask, NULL);
    if (erg < 0) {
      int prio;
      if (errno == EINTR) { prio = LOG_WARNING; } else { prio = LOG_ERR; }
      outlog(prio, "Running connections: select: %s", strerror(errno));
      return VG_FALSE;
    }
    if (erg == 0) {
      outlog(LOG_WARNING, "Running connections: timeout");
      break;
    }

    if (!FD_ISSET(udpsv->sockfd, &fdread)) { continue; }

    /* receive nw-packet */
    satmp = udpsv->salen;
    dglen = recvfrom(udpsv->sockfd, dgram, sizeof(dgram), 0, udpsv->sad, &satmp);
    if (dglen == 0) { continue; }
    if (dglen < 0) {
      if (SML3_errno_is_eagain(errno)) { continue; }
      outlog(LOG_ERR, "Running connections: recvfrom: %s", strerror(errno));
      return VG_FALSE;
    }
    slen = (size_t)dglen;

    /* set nw-packet into struct */
#ifdef NWPACK_DUMP
    if (SML3_nw_udp_peername(udpsv->sockfd, udpsv->sad, satmp, bhost, sizeof(bhost), &bport) == 0) {
      snprintf(remaddr, sizeof(remaddr), "%s:%d", bhost, bport);
    } else {
      snprintf(remaddr, sizeof(remaddr), "(no-addr)");
    }
    sv_dgram2nwpack(remaddr, dgram, slen, &nwpack);
#else
    sv_dgram2nwpack(NULL, dgram, slen, &nwpack);
#endif
    if (nwpack.kenner != NWDATA_KENNER_RUNCONN) { continue; }

    /* process request */

    if (nwpack.action == NWDATA_ACTION_CLOSE) {  /* close connection */
      iconn = NW_CLNR2ICONN(nwpack.u.k_runconn.a_close.c2s.clnr);
      /* check if valid */
      if (iconn < 0 || iconn >= udpsv->maxconn) { continue; }
      if (satmp != udpsv->conn[iconn].salen || !nw_addr_equal(udpsv->ipv, udpsv->sad, udpsv->conn[iconn].sad, satmp)) { continue; }
      if (!udpsv->conn[iconn].active) { send_close(udpsv, iconn); continue; }

      /* close */
      send_close(udpsv, iconn);
      nw_conncl_set(&udpsv->conncl, NW_ICONN2CLNR(iconn), 0);
      udpsv->conn[iconn].active = VG_FALSE;
      outlog(LOG_NOTICE, "Client %d [%s] closed connection", NW_ICONN2CLNR(iconn), udpsv->conn[iconn].hname);
      if (no_more_conn(udpsv)) { break; }

    } else if (nwpack.action == NWDATA_ACTION_PING) {  /* ping */
      iconn = NW_CLNR2ICONN(nwpack.u.k_runconn.a_ping.c2s.clnr);
      /* check if valid */
      if (iconn < 0 || iconn >= udpsv->maxconn) { continue; }
      if (satmp != udpsv->conn[iconn].salen || !nw_addr_equal(udpsv->ipv, udpsv->sad, udpsv->conn[iconn].sad, satmp)) { continue; }
      if (!udpsv->conn[iconn].active) { continue; }

      if (!pause) { /* send continue-packet to client again */
        memset(&nwpack, 0, sizeof(nwpack));
        nwpack.kenner = NWDATA_KENNER_RUNCONN;
        nwpack.action = NWDATA_ACTION_CONTINUE;
        send2client(udpsv, &nwpack, dgram, iconn);
      }

    } else if (nwpack.action == NWDATA_ACTION_PAUSE) {  /* pause */
      iconn = NW_CLNR2ICONN(nwpack.u.k_runconn.a_pause.c2s.clnr);
      /* check if valid */
      if (iconn < 0 || iconn >= udpsv->maxconn) { continue; }
      if (satmp != udpsv->conn[iconn].salen || !nw_addr_equal(udpsv->ipv, udpsv->sad, udpsv->conn[iconn].sad, satmp)) { continue; }
      if (!udpsv->conn[iconn].active) { send_close(udpsv, iconn); continue; }

      /* send pause-packet */
      memset(&nwpack, 0, sizeof(nwpack));
      nwpack.kenner = NWDATA_KENNER_RUNCONN;
      nwpack.action = NWDATA_ACTION_PAUSE;
      if (!pause) {  /* send to all clients */
        send2client(udpsv, &nwpack, dgram, -1);
        pause = VG_TRUE;
      } else {  /* send to client again */
        send2client(udpsv, &nwpack, dgram, iconn);
      }

    } else if (nwpack.action == NWDATA_ACTION_CONTINUE) {  /* continue */
      iconn = NW_CLNR2ICONN(nwpack.u.k_runconn.a_continue.c2s.clnr);
      /* check if valid */
      if (iconn < 0 || iconn >= udpsv->maxconn) { continue; }
      if (satmp != udpsv->conn[iconn].salen || !nw_addr_equal(udpsv->ipv, udpsv->sad, udpsv->conn[iconn].sad, satmp)) { continue; }
      if (!udpsv->conn[iconn].active) { send_close(udpsv, iconn); continue; }

      /* send continue-packet */
      memset(&nwpack, 0, sizeof(nwpack));
      nwpack.kenner = NWDATA_KENNER_RUNCONN;
      nwpack.action = NWDATA_ACTION_CONTINUE;
      if (pause) {  /* send to all clients */
        send2client(udpsv, &nwpack, dgram, -1);
        pause = VG_FALSE;
      } else {  /* send to client again */
        send2client(udpsv, &nwpack, dgram, iconn);
      }

    } else if (nwpack.action == NWDATA_ACTION_KEYS) {  /* keys */
      iconn = NW_CLNR2ICONN(nwpack.u.k_runconn.a_keys.c2s.clnr);
      /* check if valid */
      if (iconn < 0 || iconn >= udpsv->maxconn) { continue; }
      if (satmp != udpsv->conn[iconn].salen || !nw_addr_equal(udpsv->ipv, udpsv->sad, udpsv->conn[iconn].sad, satmp)) { continue; }
      if (!udpsv->conn[iconn].active) { send_close(udpsv, iconn); continue; }
      if (pause) { continue; }

      /* set data */

      /* set new current element of data to fill, if client's sequence-number is seqnr_fill */
      if (nwpack.u.k_runconn.a_keys.c2s.seqnr == seqnr_fill) {
        int iconn2;
        unsigned short seqnr_old;

        /* update connection-bits for current element */
        udpsv->nwkeys.data[udpsv->nwkeys.pos].conncl = udpsv->conncl;

        /* get next element */
        if (++udpsv->nwkeys.pos == udpsv->nwkeys.max) { udpsv->nwkeys.pos = 0; }
        seqnr_old = udpsv->nwkeys.data[udpsv->nwkeys.pos].seqnr;
        memset(&udpsv->nwkeys.data[udpsv->nwkeys.pos], 0, sizeof(*udpsv->nwkeys.data));
        udpsv->nwkeys.data[udpsv->nwkeys.pos].seqnr = ++seqnr_fill;

        /* check for outdated clients */
        for (iconn2 = 0; iconn2 < udpsv->maxconn; iconn2++) {
          if (!udpsv->conn[iconn2].active) { continue; }
          if (udpsv->conn[iconn2].seqnr == seqnr_old) {  /* outdated, close client-connection */
            send_close(udpsv, iconn2);
            nw_conncl_set(&udpsv->conncl, NW_ICONN2CLNR(iconn2), 0);
            udpsv->conn[iconn2].active = VG_FALSE;
            outlog(LOG_WARNING, "Outdated client %d [%s] closed", NW_ICONN2CLNR(iconn2), udpsv->conn[iconn2].hname);
          }
        }
        udpsv->nwkeys.data[udpsv->nwkeys.pos].conncl = udpsv->conncl;
        if (no_more_conn(udpsv)) { break; }

        /* check for additional-data to be sent */
        udpsv->nwkeys.data[udpsv->nwkeys.pos].zdatalen = 0;
        if (udpsv->zdata_list != NULL) {
          if (udpsv->zdata_list->pos == 0) {  /* new additional-data, set header */
            int zlen = nw_zheader_set(udpsv->zdata_list, udpsv->nwkeys.data[udpsv->nwkeys.pos].zdata);
            udpsv->nwkeys.data[udpsv->nwkeys.pos].zdatalen = zlen;
          }

          /* set data */
          ilen = (int)(udpsv->zdata_list->size - udpsv->zdata_list->pos);
          if (ilen > MAX_ZDATA - udpsv->nwkeys.data[udpsv->nwkeys.pos].zdatalen) {
            ilen = MAX_ZDATA - udpsv->nwkeys.data[udpsv->nwkeys.pos].zdatalen;
          }
          if (ilen > 0) {
            memcpy(&udpsv->nwkeys.data[udpsv->nwkeys.pos].zdata[udpsv->nwkeys.data[udpsv->nwkeys.pos].zdatalen],
                   &udpsv->zdata_list->data[udpsv->zdata_list->pos],
                   (size_t)ilen);
            udpsv->zdata_list->pos += ilen;
            udpsv->nwkeys.data[udpsv->nwkeys.pos].zdatalen += ilen;
          }

          /* data complete? */
          if (udpsv->zdata_list->pos == udpsv->zdata_list->size) {  /* remove element */
            struct nwdata_zdata *znext = udpsv->zdata_list->next;
            if (udpsv->zdata_list->data != NULL) { free(udpsv->zdata_list->data); }
            free(udpsv->zdata_list);
            udpsv->zdata_list = znext;
          }
        }
      }

      /* transfer client's data, if no repetition of client's last request */
      if (nwpack.u.k_runconn.a_keys.c2s.seqnr != udpsv->conn[iconn].seqnr) {
        /* keys-data */
        memcpy(&udpsv->nwkeys.data[udpsv->nwkeys.pos].keysdata[MAX_KEYSBYT * iconn], nwpack.u.k_runconn.a_keys.c2s.keysdata, MAX_KEYSBYT);
        udpsv->nwkeys.data[udpsv->nwkeys.pos].keyset |= (1 << iconn);  /* client has sent keys */
        udpsv->conn[iconn].seqnr = nwpack.u.k_runconn.a_keys.c2s.seqnr;

        /* additional-data */
        if (nwpack.u.k_runconn.a_keys.c2s.zdatalen > 0) {
          char *zdata = nwpack.u.k_runconn.a_keys.c2s.zdata;
          if (udpsv->conn[iconn].zdata == NULL) {  /* new additional-data, read header */
            int zlen;
            udpsv->conn[iconn].zdata = SML3_calloc(1, sizeof(*udpsv->conn[0].zdata));
            zlen = nw_zheader_get(udpsv->conn[iconn].zdata, zdata);
            udpsv->conn[iconn].zdata->pos = 0;
            udpsv->conn[iconn].zdata->next = NULL;
            nwpack.u.k_runconn.a_keys.c2s.zdatalen -= zlen;
            zdata += zlen;
          }
          /* set data */
          if (nwpack.u.k_runconn.a_keys.c2s.zdatalen > 0) {
            memcpy(&udpsv->conn[iconn].zdata->data[udpsv->conn[iconn].zdata->pos],
                   zdata, 
                   (size_t)nwpack.u.k_runconn.a_keys.c2s.zdatalen);
            udpsv->conn[iconn].zdata->pos += nwpack.u.k_runconn.a_keys.c2s.zdatalen;
          }

          /* data complete? */
          if (udpsv->conn[iconn].zdata->pos == udpsv->conn[iconn].zdata->size) {
            struct nwdata_zdata **znextp;
            udpsv->conn[iconn].zdata->pos = 0;
            for (znextp = &udpsv->zdata_list; *znextp != NULL; znextp = &(*znextp)->next) {;}
            *znextp = udpsv->conn[iconn].zdata;
            udpsv->conn[iconn].zdata = NULL;
          }
        }
      }

      /* send response */
      memset(&nwpack, 0, sizeof(nwpack));
      nwpack.kenner = NWDATA_KENNER_RUNCONN;
      nwpack.action = NWDATA_ACTION_KEYS;
      if (udpsv->conn[iconn].seqnr > seqnr_fill) {
        unsigned int sqfill = (unsigned int)seqnr_fill + ((unsigned int)(unsigned short)-1 + 1);
        nwpack.u.k_runconn.a_keys.s2c.anz = sqfill - udpsv->conn[iconn].seqnr;
      } else {
        nwpack.u.k_runconn.a_keys.s2c.anz = seqnr_fill - udpsv->conn[iconn].seqnr;
      }
      if (nwpack.u.k_runconn.a_keys.s2c.anz > udpsv->nwkeys.pos) {
        int start, anz;
        start = udpsv->nwkeys.pos + udpsv->nwkeys.max - nwpack.u.k_runconn.a_keys.s2c.anz;
        if (nwpack.u.k_runconn.a_keys.s2c.anz > MAX_KEYSPACKETS) { nwpack.u.k_runconn.a_keys.s2c.anz = MAX_KEYSPACKETS; }
        anz = udpsv->nwkeys.max - start;
        if (anz > nwpack.u.k_runconn.a_keys.s2c.anz) { anz = nwpack.u.k_runconn.a_keys.s2c.anz; }
        memcpy(nwpack.u.k_runconn.a_keys.s2c.data, &udpsv->nwkeys.data[start], sizeof(*udpsv->nwkeys.data) * anz);
        if (anz < nwpack.u.k_runconn.a_keys.s2c.anz) {
          memcpy(&nwpack.u.k_runconn.a_keys.s2c.data[anz], udpsv->nwkeys.data, sizeof(*udpsv->nwkeys.data) * (nwpack.u.k_runconn.a_keys.s2c.anz - anz));
        }
      } else {
        int start = udpsv->nwkeys.pos - nwpack.u.k_runconn.a_keys.s2c.anz;
        if (nwpack.u.k_runconn.a_keys.s2c.anz > MAX_KEYSPACKETS) { nwpack.u.k_runconn.a_keys.s2c.anz = MAX_KEYSPACKETS; }
        memcpy(nwpack.u.k_runconn.a_keys.s2c.data, &udpsv->nwkeys.data[start], sizeof(*udpsv->nwkeys.data) * nwpack.u.k_runconn.a_keys.s2c.anz);
      }
      { int anz = nwpack.u.k_runconn.a_keys.s2c.anz;
        if (anz > MAX_PKSENT) { anz = MAX_PKSENT; }
        udpsv->conn[iconn].pksent[anz - 1]++;
      }
      send2client(udpsv, &nwpack, dgram, iconn);

    } else if (nwpack.action == NWDATA_ACTION_CONNECTED) {  /* client wants to receive connected-client-info */
      iconn = NW_CLNR2ICONN(nwpack.u.k_runconn.a_connected.c2s.clnr);
      /* check if valid */
      if (iconn < 0 || iconn >= udpsv->maxconn) { continue; }
      if (satmp != udpsv->conn[iconn].salen || !nw_addr_equal(udpsv->ipv, udpsv->sad, udpsv->conn[iconn].sad, satmp)) { continue; }
      if (!udpsv->conn[iconn].active) { send_close(udpsv, iconn); continue; }

      /* send connected-client-info-packet */
      memset(&nwpack, 0, sizeof(nwpack));
      nwpack.kenner = NWDATA_KENNER_RUNCONN;
      nwpack.action = NWDATA_ACTION_CONNECTED;
      nwpack.u.k_runconn.a_connected.s2c.conncl = udpsv->conncl;
      send2client(udpsv, &nwpack, dgram, iconn);

    } else if (nwpack.action == NWDATA_ACTION_XDATA_RECV) {  /* client wants to receive exchange-data */
      struct nwdata_packets nwpack_resp;

      iconn = NW_CLNR2ICONN(nwpack.u.k_runconn.a_xdata_recv.c2s.clnr);
      /* check if valid */
      if (iconn < 0 || iconn >= udpsv->maxconn) { continue; }
      if (satmp != udpsv->conn[iconn].salen || !nw_addr_equal(udpsv->ipv, udpsv->sad, udpsv->conn[iconn].sad, satmp)) { continue; }
      if (!udpsv->conn[iconn].active) { continue; }
      if (pause) { continue; }

      memset(&nwpack_resp, 0, sizeof(nwpack_resp));
      nwpack_resp.kenner = NWDATA_KENNER_RUNCONN;
      nwpack_resp.action = NWDATA_ACTION_XDATA_SEND;
      nwpack_resp.u.k_runconn.a_xdata_send.s2c.xdatalen = 0;

      if (nwpack.u.k_runconn.a_xdata_recv.c2s.seqnr == (unsigned short)(udpsv->conn[iconn].xdata.seqnr + 1)) {
        /* next sequence-number */
        udpsv->conn[iconn].xdata.seqnr = nwpack.u.k_runconn.a_xdata_recv.c2s.seqnr;
        nwpack_resp.u.k_runconn.a_xdata_send.s2c.seqnr = udpsv->conn[iconn].xdata.seqnr;

        /* set to first exchange-data list-element? */
        if (udpsv->conn[iconn].xdata.data_send == NULL) {
          udpsv->conn[iconn].xdata.data_send = udpsv->xdata_list;
          udpsv->conn[iconn].xdata.posv = udpsv->conn[iconn].xdata.posb = 0;
          if (udpsv->conn[iconn].xdata.data_send == NULL) { send_empty_xdata(udpsv, &nwpack_resp, dgram, iconn); continue; }
        }

        /* if current list-element already sent, switch to next */
        if (udpsv->conn[iconn].xdata.posb == (int)udpsv->conn[iconn].xdata.data_send->size) {
          udpsv->conn[iconn].xdata.posv = udpsv->conn[iconn].xdata.posb;

          /* check if oldest exchange-data list-element can be removed */
          if (udpsv->conn[iconn].xdata.data_send == udpsv->xdata_list) {
            int iconn2;
            /* any client still uses it? */
            for (iconn2 = 0; iconn2 < udpsv->maxconn; iconn2++) {
              if (!udpsv->conn[iconn2].active) { continue; }
              if (udpsv->conn[iconn2].xdata.data_send == NULL) { break; }
              if (udpsv->conn[iconn2].xdata.data_send == udpsv->xdata_list) {
                if (udpsv->conn[iconn2].xdata.posb < (int)udpsv->conn[iconn2].xdata.data_send->size) { break; }
                if (udpsv->conn[iconn].xdata.posv < udpsv->conn[iconn].xdata.posb) { break; }
              }
            }
            /* if not, switch relevant clients to next list-element and then remove it */
            if (iconn2 == udpsv->maxconn) {
              struct nwdata_xdata *xnext = udpsv->xdata_list->next;
              for (iconn2 = 0; iconn2 < udpsv->maxconn; iconn2++) {
                if (udpsv->conn[iconn2].xdata.data_send == udpsv->xdata_list) {
                  udpsv->conn[iconn2].xdata.data_send = udpsv->conn[iconn2].xdata.data_send->next;
                  udpsv->conn[iconn2].xdata.posv = udpsv->conn[iconn2].xdata.posb = 0;
                }
              }
              /* remove it */
              if (udpsv->xdata_list->data != NULL) { free(udpsv->xdata_list->data); }
              free(udpsv->xdata_list);
              udpsv->xdata_list = xnext;
            }
          }
          if (udpsv->conn[iconn].xdata.data_send == NULL) { send_empty_xdata(udpsv, &nwpack_resp, dgram, iconn); continue; }

          /* still current list-element, now switch */
          if (udpsv->conn[iconn].xdata.posb == (int)udpsv->conn[iconn].xdata.data_send->size) {
            if (udpsv->conn[iconn].xdata.data_send->next == NULL) { send_empty_xdata(udpsv, &nwpack_resp, dgram, iconn); continue; }
            udpsv->conn[iconn].xdata.data_send = udpsv->conn[iconn].xdata.data_send->next;
            udpsv->conn[iconn].xdata.posv = udpsv->conn[iconn].xdata.posb = 0;
          }
        }

        /* send new data chunk beginning at posb */
        udpsv->conn[iconn].xdata.posv = udpsv->conn[iconn].xdata.posb;
        if (udpsv->conn[iconn].xdata.posv == 0) {  /* new exchange-data, set header */
          int xlen;
          udpsv->conn[iconn].xdata.data_send->conncl = udpsv->conncl;
          xlen = nw_xheader_set(udpsv->conn[iconn].xdata.data_send, nwpack_resp.u.k_runconn.a_xdata_send.s2c.xdata);
          nwpack_resp.u.k_runconn.a_xdata_send.s2c.xdatalen = xlen;
        }
        ilen = udpsv->conn[iconn].xdata.data_send->size - udpsv->conn[iconn].xdata.posb;
        if (ilen > MAX_XDATA - nwpack_resp.u.k_runconn.a_xdata_send.s2c.xdatalen) {
          ilen = MAX_XDATA - nwpack_resp.u.k_runconn.a_xdata_send.s2c.xdatalen;
        }
        udpsv->conn[iconn].xdata.posb += ilen;
        memcpy(nwpack_resp.u.k_runconn.a_xdata_send.s2c.xdata + nwpack_resp.u.k_runconn.a_xdata_send.s2c.xdatalen,
               udpsv->conn[iconn].xdata.data_send->data + udpsv->conn[iconn].xdata.posv,
               (size_t)ilen);
        nwpack_resp.u.k_runconn.a_xdata_send.s2c.xdatalen += ilen;
        send2client(udpsv, &nwpack_resp, dgram, iconn);

      } else if (nwpack.u.k_runconn.a_xdata_recv.c2s.seqnr == udpsv->conn[iconn].xdata.seqnr) {
        /* previous sequence-number again */
        nwpack_resp.u.k_runconn.a_xdata_send.s2c.seqnr = udpsv->conn[iconn].xdata.seqnr;

        /* nothing to send? */
        if (udpsv->conn[iconn].xdata.data_send == NULL
            || udpsv->conn[iconn].xdata.posv == udpsv->conn[iconn].xdata.posb) {
          send2client(udpsv, &nwpack_resp, dgram, iconn);
          continue;
        }

        /* resend previous data chunk beginning at posv */
        if (udpsv->conn[iconn].xdata.posv == 0) {  /* new exchange-data, set header */
          int xlen;
          udpsv->conn[iconn].xdata.data_send->conncl = udpsv->conncl;
          xlen = nw_xheader_set(udpsv->conn[iconn].xdata.data_send, nwpack_resp.u.k_runconn.a_xdata_send.s2c.xdata);
          nwpack_resp.u.k_runconn.a_xdata_send.s2c.xdatalen = xlen;
        }
        ilen = udpsv->conn[iconn].xdata.posb - udpsv->conn[iconn].xdata.posv;
        memcpy(nwpack_resp.u.k_runconn.a_xdata_send.s2c.xdata + nwpack_resp.u.k_runconn.a_xdata_send.s2c.xdatalen,
               udpsv->conn[iconn].xdata.data_send->data + udpsv->conn[iconn].xdata.posv,
               (size_t)ilen);
        nwpack_resp.u.k_runconn.a_xdata_send.s2c.xdatalen += ilen;
        send2client(udpsv, &nwpack_resp, dgram, iconn);
      }

    } else if (nwpack.action == NWDATA_ACTION_XDATA_SEND) {  /* client sends exchange-data */
      struct nwdata_packets nwpack_resp;

      iconn = NW_CLNR2ICONN(nwpack.u.k_runconn.a_xdata_send.c2s.clnr);
      /* check if valid */
      if (iconn < 0 || iconn >= udpsv->maxconn) { continue; }
      if (satmp != udpsv->conn[iconn].salen || !nw_addr_equal(udpsv->ipv, udpsv->sad, udpsv->conn[iconn].sad, satmp)) { continue; }
      if (!udpsv->conn[iconn].active) { continue; }
      if (pause) { continue; }

      memset(&nwpack_resp, 0, sizeof(nwpack_resp));
      nwpack_resp.kenner = NWDATA_KENNER_RUNCONN;
      nwpack_resp.action = NWDATA_ACTION_XDATA_RECV;

      if (nwpack.u.k_runconn.a_xdata_send.c2s.seqnr == (unsigned short)(udpsv->conn[iconn].xdata.seqnr + 1)) {
        /* next sequence-number */
        udpsv->conn[iconn].xdata.seqnr = nwpack.u.k_runconn.a_xdata_send.c2s.seqnr;
        nwpack_resp.u.k_runconn.a_xdata_recv.s2c.seqnr = udpsv->conn[iconn].xdata.seqnr;

        /* exchange-data */
        if (nwpack.u.k_runconn.a_xdata_send.c2s.xdatalen > 0) {
          char *xdata = nwpack.u.k_runconn.a_xdata_send.c2s.xdata;
          if (udpsv->conn[iconn].xdata.data_recv == NULL) {  /* new exchange-data, read header */
            int xlen;
            udpsv->conn[iconn].xdata.data_recv = SML3_calloc(1, sizeof(*udpsv->conn[0].xdata.data_recv));
            xlen = nw_xheader_get(udpsv->conn[iconn].xdata.data_recv, xdata);
            udpsv->conn[iconn].xdata.data_recv->pos = 0;
            udpsv->conn[iconn].xdata.data_recv->next = NULL;
            nwpack.u.k_runconn.a_xdata_send.c2s.xdatalen -= xlen;
            xdata += xlen;
          }

          /* set data */
          if (nwpack.u.k_runconn.a_xdata_send.c2s.xdatalen > 0) {
            memcpy(&udpsv->conn[iconn].xdata.data_recv->data[udpsv->conn[iconn].xdata.data_recv->pos],
                   xdata, 
                   (size_t)nwpack.u.k_runconn.a_xdata_send.c2s.xdatalen);
            udpsv->conn[iconn].xdata.data_recv->pos += nwpack.u.k_runconn.a_xdata_send.c2s.xdatalen;
          }

          /* data complete? */
          if (udpsv->conn[iconn].xdata.data_recv->pos == udpsv->conn[iconn].xdata.data_recv->size) {
            struct nwdata_xdata **xnextp;
            udpsv->conn[iconn].xdata.data_recv->pos = 0;
            for (xnextp = &udpsv->xdata_list; *xnextp != NULL; xnextp = &(*xnextp)->next) {;}
            *xnextp = udpsv->conn[iconn].xdata.data_recv;
            udpsv->conn[iconn].xdata.data_recv = NULL;
          }
        }

        /* send acknowledgement */
        send2client(udpsv, &nwpack_resp, dgram, iconn);

      } else if (nwpack.u.k_runconn.a_xdata_send.c2s.seqnr == udpsv->conn[iconn].xdata.seqnr) {
        /* previous sequence-number again */
        nwpack_resp.u.k_runconn.a_xdata_recv.s2c.seqnr = udpsv->conn[iconn].xdata.seqnr;

        /* resend acknowledgement */
        send2client(udpsv, &nwpack_resp, dgram, iconn);
      }
    }
  }

  return VG_TRUE;
} /* Ende run_connections */
