/*
 * dxlAPRS toolchain
 *
 * Copyright (C) Christian Rabler <oe5dxl@oevsv.at>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */


#define X2C_int32
#define X2C_index32
#ifndef X2C_H_
#include "X2C.h"
#endif
#define udphub_C_
#ifndef osi_H_
#include "osi.h"
#endif
#include <osic.h>
#ifndef udp_H_
#include "udp.h"
#endif
#ifndef aprsstr_H_
#include "aprsstr.h"
#endif
#ifndef Select_H_
#include "Select.h"
#endif

/* axudp bidirectional digi - user hub by OE5DXL */
#define udphub_CALLLEN 7

#define udphub_MAXLEN 338

#define udphub_MINLEN 17

static uint32_t udphub_POLYNOM = 0x8408UL;

static uint32_t udphub_CRCINIT = 0xFFFFUL;

#define udphub_SOURCECALL 7

#define udphub_DESTCALL 0

#define udphub_MAXFD 31

#define udphub_cCOM "#"

#define udphub_cSPOOF "p"

#define udphub_cBCIN "b"

#define udphub_cBCOUT "B"

#define udphub_cSENDALL "A"

#define udphub_LF "\012"

#define udphub_TICKER 15

typedef char RAWCALL[7];


struct TIMEVAL {
   int32_t tvsec;
   int32_t tvusec;
};

struct USER;

typedef struct USER * pUSER;


struct USER {
   pUSER next;
   RAWCALL call;
   uint32_t uip;
   uint32_t dport;
   char datagot; /* not send same data twice to same ip/port */
   char bcin; /* broadcast in accept */
   char bcout; /* broadcast out allowed */
   char willall; /* like host send all to */
   char nopurge; /* entry from file no purge */
   char nospoof; /* not overwrite ip:port */
   uint32_t htime;
};

static uint8_t CRCL[256];

static uint8_t CRCH[256];

static char noinf;

static char modified;

static char peertopeer;

static char defaultbcout;

/* no broadcast send to new users */
static char defaultbcin;

static char show;

/* no broadcast accept for new users */
static char checkdigiip;

static pUSER users;

static uint32_t alllifetime;

static uint32_t systime;

/* time for all/unknown ssid */
static uint32_t lifetime;

static uint32_t touserport;

static uint32_t todigiport;

static uint32_t digiip;

static int32_t digisock;

static int32_t usersock;

static uint32_t maxentries;

static char initfn[1025];

static char wrfn[1025];

static RAWCALL broadcastdest;
/*
PROCEDURE ["C"] / select(n: INTEGER; readfds: ADDRESS; writefds: ADDRESS;
                         exceptfds: ADDRESS; timeout: ADDRESS) : INTEGER;
*/


static void Err(const char text[], uint32_t text_len)
{
   osi_WrStr("udphub: ", 9ul);
   osi_WrStr(text, text_len);
   osi_WrStrLn(" error abort", 13ul);
   X2C_ABORT();
} /* end Err() */

#define udphub_POLINOM 0x8408 


static void Gencrctab(void)
{
   uint32_t c;
   uint32_t crc;
   uint32_t i;
   for (c = 0UL; c<=255UL; c++) {
      crc = 255UL-c;
      for (i = 0UL; i<=7UL; i++) {
         if ((crc&1)) crc = (uint32_t)((uint32_t)(crc>>1)^0x8408UL);
         else crc = crc>>1;
      } /* end for */
      CRCL[c] = (uint8_t)crc;
      CRCH[c] = (uint8_t)(255UL-(crc>>8));
   } /* end for */
} /* end Gencrctab() */


static void WCh(char c)
{
   if (c!='\015') {
      if ((uint8_t)c<' ' || (uint8_t)c>='\177') osi_WrStr(".", 2ul);
      else osi_WrStr((char *) &c, 1u/1u);
   }
} /* end WCh() */


static void ShowCall(char f[], uint32_t f_len, uint32_t pos)
{
   uint32_t e;
   uint32_t i;
   uint32_t tmp;
   char tmp0;
   e = pos;
   tmp = pos+5UL;
   i = pos;
   if (i<=tmp) for (;; i++) {
      if (f[i]!='@') e = i;
      if (i==tmp) break;
   } /* end for */
   tmp = e;
   i = pos;
   if (i<=tmp) for (;; i++) {
      WCh((char)((uint32_t)(uint8_t)f[i]>>1));
      if (i==tmp) break;
   } /* end for */
   i = (uint32_t)(uint8_t)f[pos+6UL]>>1&15UL;
   if (i) {
      osi_WrStr("-", 2ul);
      if (i>=10UL) {
         osi_WrStr((char *)(tmp0 = (char)(i/10UL+48UL),&tmp0),
                1u/1u);
      }
      osi_WrStr((char *)(tmp0 = (char)(i%10UL+48UL),&tmp0), 1u/1u);
   }
} /* end ShowCall() */

static uint32_t udphub_UA = 0x63UL;

static uint32_t udphub_DM = 0xFUL;

static uint32_t udphub_SABM = 0x2FUL;

static uint32_t udphub_DISC = 0x43UL;

static uint32_t udphub_FRMR = 0x87UL;

static uint32_t udphub_UI = 0x3UL;

static uint32_t udphub_RR = 0x1UL;

static uint32_t udphub_REJ = 0x9UL;

static uint32_t udphub_RNR = 0x5UL;


static void Showctl(uint32_t com, uint32_t cmd)
{
   uint32_t cm;
   char PF[4];
   char tmp;
   osi_WrStr(" ctl ", 6ul);
   cm = (uint32_t)cmd&~0x10UL;
   if ((cm&0xFUL)==0x1UL) {
      osi_WrStr("RR", 3ul);
      osi_WrStr((char *)(tmp = (char)(48UL+(cmd>>5)),&tmp), 1u/1u);
   }
   else if ((cm&0xFUL)==0x5UL) {
      osi_WrStr("RNR", 4ul);
      osi_WrStr((char *)(tmp = (char)(48UL+(cmd>>5)),&tmp), 1u/1u);
   }
   else if ((cm&0xFUL)==0x9UL) {
      osi_WrStr("REJ", 4ul);
      osi_WrStr((char *)(tmp = (char)(48UL+(cmd>>5)),&tmp), 1u/1u);
   }
   else if ((cm&0x1UL)==0UL) {
      osi_WrStr("I", 2ul);
      osi_WrStr((char *)(tmp = (char)(48UL+(cmd>>5)),&tmp), 1u/1u);
      osi_WrStr((char *)(tmp = (char)(48UL+(cmd>>1&7UL)),&tmp),
                1u/1u);
   }
   else if (cm==0x3UL) osi_WrStr("UI", 3ul);
   else if (cm==0xFUL) osi_WrStr("DM", 3ul);
   else if (cm==0x2FUL) osi_WrStr("SABM", 5ul);
   else if (cm==0x43UL) osi_WrStr("DISC", 5ul);
   else if (cm==0x63UL) osi_WrStr("UA", 3ul);
   else if (cm==0x87UL) osi_WrStr("FRMR", 5ul);
   else osi_WrHex(cmd, 1UL);
   strncpy(PF,"v^-+",4u);
   if (com==0UL || com==3UL) osi_WrStr("v1", 3ul);
   else {
      osi_WrStr((char *) &PF[(com&1UL)+2UL*(uint32_t)
                ((0x10UL & (uint32_t)cmd)!=0)], 1u/1u);
   }
} /* end Showctl() */


static void ShowFrame(char f[], uint32_t f_len, uint32_t len,
                char noinfo)
{
   uint32_t i;
   char d;
   char v;
   i = 0UL;
   while (!((uint32_t)(uint8_t)f[i]&1)) {
      ++i;
      if (i>len) return;
   }
   /* no address end mark found */
   if (i%7UL!=6UL) return;
   /* address end not modulo 7 error */
   osi_WrStr(" fm ", 5ul);
   ShowCall(f, f_len, 7UL);
   osi_WrStr(" to ", 5ul);
   ShowCall(f, f_len, 0UL);
   i = 14UL;
   v = 1;
   while (i+6UL<len && !((uint32_t)(uint8_t)f[i-1UL]&1)) {
      if (v) {
         osi_WrStr(" via", 5ul);
         v = 0;
      }
      osi_WrStr(" ", 2ul);
      ShowCall(f, f_len, i);
      if ((uint32_t)(uint8_t)f[i+6UL]>=128UL && (((uint32_t)(uint8_t)
                f[i+6UL]&1) || (uint32_t)(uint8_t)f[i+13UL]<128UL)) {
         osi_WrStr("*", 2ul);
      }
      i += 7UL;
   }
   Showctl((uint32_t)((0x80U & (uint8_t)(uint8_t)f[6UL])!=0)
                +2UL*(uint32_t)((0x80U & (uint8_t)(uint8_t)f[13UL])!=0)
                , (uint32_t)(uint8_t)f[i]);
   ++i;
   if (i<len) {
      osi_WrStr(" pid ", 6ul);
      osi_WrHex((uint32_t)(uint8_t)f[i], 1UL);
   }
   ++i;
   osic_WrLn();
   if (!noinfo) {
      d = 0;
      while (i<len) {
         if (f[i]!='\015') {
            WCh(f[i]);
            d = 1;
         }
         else if (d) {
            osic_WrLn();
            d = 0;
         }
         ++i;
      }
      if (d) osic_WrLn();
   }
} /* end ShowFrame() */


static char testCRC(char frame[], uint32_t frame_len,
                int32_t size)
{
   uint8_t h;
   uint8_t l;
   uint8_t b;
   int32_t i;
   int32_t tmp;
   l = 0U;
   h = 0U;
   tmp = size-3L;
   i = 0L;
   if (i<=tmp) for (;; i++) {
      b = (uint8_t)((uint8_t)(uint8_t)frame[i]^l);
      l = CRCL[b]^h;
      h = CRCH[b];
      if (i==tmp) break;
   } /* end for */
   return frame[size-2L]==(char)l && frame[size-1L]==(char)h;
} /* end testCRC() */

/*
PROCEDURE GetIp1(h:ARRAY OF CHAR; VAR ip:IPNUM; VAR dp:UDPPORT):INTEGER;
CONST PORTSEP=":";
      DEFAULTIP=7F000001H;

VAR i, p, n:CARDINAL;
    ok:BOOLEAN;
BEGIN
  p:=0;
  h[HIGH(h)]:=0C;
  ip:=0;
  FOR i:=0 TO 4 DO
    IF (i>=3) OR (h[0]<>PORTSEP) THEN
      n:=0;
      ok:=FALSE;
      WHILE (h[p]>="0") & (h[p]<="9") DO
        ok:=TRUE;
        n:=n*10+ORD(h[p])-ORD("0");
        INC(p);
      END;
      IF NOT ok THEN RETURN -1 END;

    END;
    IF i<3 THEN
      IF h[0]<>PORTSEP THEN
        IF (h[p]<>".") OR (n>255) THEN RETURN -1 END;

        ip:=ip*256+n;
      END;

    ELSIF i=3 THEN
      IF h[0]<>PORTSEP THEN
        ip:=ip*256+n;
        IF (h[p]<>PORTSEP) OR (n>255) THEN RETURN -1 END;

      ELSE p:=0; ip:=DEFAULTIP END;

    ELSIF n>65535 THEN RETURN -1 END;

    dp:=n;
    INC(p);
  END;
  RETURN 0
END GetIp1;
*/

static int32_t GetIp1(char h[], uint32_t h_len, uint32_t * ip,
                uint32_t * port)
{
   uint32_t p;
   uint32_t n;
   uint32_t i;
   char ok0;
   int32_t GetIp1_ret;
   X2C_PCOPY((void **)&h,h_len);
   p = 0UL;
   h[h_len-1] = 0;
   *ip = 0UL;
   for (i = 0UL; i<=4UL; i++) {
      n = 0UL;
      ok0 = 0;
      while ((uint8_t)h[p]>='0' && (uint8_t)h[p]<='9') {
         ok0 = 1;
         n = (n*10UL+(uint32_t)(uint8_t)h[p])-48UL;
         ++p;
      }
      if (!ok0) {
         GetIp1_ret = -1L;
         goto label;
      }
      if (i<3UL) {
         if (h[p]!='.' || n>255UL) {
            GetIp1_ret = -1L;
            goto label;
         }
         *ip =  *ip*256UL+n;
      }
      else if (i==3UL) {
         *ip =  *ip*256UL+n;
         if (h[p]!=':' || n>255UL) {
            GetIp1_ret = -1L;
            goto label;
         }
      }
      else if (n>65535UL) {
         GetIp1_ret = -1L;
         goto label;
      }
      *port = n;
      ++p;
   } /* end for */
   GetIp1_ret = 0L;
   label:;
   X2C_PFREE(h);
   return GetIp1_ret;
} /* end GetIp1() */


static int32_t getudp(int32_t fd, char buf[], uint32_t buf_len,
                uint32_t * fromip0, uint32_t * fromport,
                char checkip)
{
   uint32_t ip;
   int32_t len;
   len = udpreceive(fd, buf, (int32_t)(buf_len), fromport, &ip);
   if (len<2L || !testCRC(buf, buf_len, len)) {
      if (show) osi_WrStrLn(" axudp crc error ", 18ul);
      return -1L;
   }
   if (checkip && *fromip0!=ip) return -2L;
   *fromip0 = ip;
   return len;
} /* end getudp() */


static char Call2Str(const char r[], uint32_t r_len,
                char t[], uint32_t t_len, uint32_t pos,
                uint32_t * len)
{
   uint32_t ssid;
   uint32_t e;
   uint32_t i;
   char c;
   uint32_t tmp;
   e = pos;
   tmp = pos+5UL;
   i = pos;
   if (i<=tmp) for (;; i++) {
      if (r[i]!='@') e = i;
      if (i==tmp) break;
   } /* end for */
   tmp = e;
   i = pos;
   if (i<=tmp) for (;; i++) {
      c = (char)((uint32_t)(uint8_t)r[i]>>1);
      if ((uint8_t)c<=' ') {
         t[*len] = 0;
         *len = 0UL;
         return 0;
      }
      t[*len] = c;
      ++*len;
      if (i==tmp) break;
   } /* end for */
   ssid = (uint32_t)(uint8_t)r[pos+6UL]>>1&15UL;
   if (ssid>0UL) {
      t[*len] = '-';
      ++*len;
      if (ssid>9UL) {
         t[*len] = '1';
         ++*len;
      }
      t[*len] = (char)(ssid%10UL+48UL);
      ++*len;
   }
   return 1;
} /* end Call2Str() */

#define udphub_SSID "-"


static char Str2Call(char s[], uint32_t s_len, uint32_t * i,
                uint32_t p, char cb[], uint32_t cb_len)
{
   uint32_t j;
   char Str2Call_ret;
   X2C_PCOPY((void **)&s,s_len);
   j = p;
   while ((*i<=s_len-1 && (uint8_t)s[*i]>' ') && s[*i]!='-') {
      if (j<p+6UL) {
         cb[j] = (char)((uint32_t)(uint8_t)s[*i]*2UL);
         ++j;
      }
      ++*i;
   }
   while (j<p+6UL) {
      cb[j] = '@';
      ++j;
   }
   j = 0UL;
   if (s[*i]=='-') {
      ++*i;
      j = 16UL;
      if ((uint8_t)s[*i]>='0' && (uint8_t)s[*i]<='9') {
         j = (uint32_t)(uint8_t)s[*i]-48UL;
         ++*i;
      }
      if ((uint8_t)s[*i]>='0' && (uint8_t)s[*i]<='9') {
         j = (j*10UL+(uint32_t)(uint8_t)s[*i])-48UL;
         ++*i;
      }
   }
   cb[p+6UL] = (char)(j*2UL+1UL); /* ssid */
   Str2Call_ret = j<=15UL;
   X2C_PFREE(s);
   return Str2Call_ret;
} /* end Str2Call() */


static int32_t GetIp(char h[], uint32_t h_len, uint32_t * ip,
                uint32_t * dp, uint32_t * lp, int32_t * fd,
                char * check)
{
   int32_t GetIp_ret;
   X2C_PCOPY((void **)&h,h_len);
   if (aprsstr_GetIp2(h, h_len, ip, dp, lp, check)<0L) {
      GetIp_ret = -1L;
      goto label;
   }
   *fd = openudp();
   if (*fd<0L || bindudp(*fd, *lp)<0L) {
      /*OR (udp.udpnonblock(fd)<0)*/
      GetIp_ret = -1L;
      goto label;
   }
   GetIp_ret = 0L;
   label:;
   X2C_PFREE(h);
   return GetIp_ret;
} /* end GetIp() */


static char GetNum(const char h[], uint32_t h_len,
                uint32_t * n)
{
   uint32_t i;
   *n = 0UL;
   i = 0UL;
   while ((uint8_t)h[i]>='0' && (uint8_t)h[i]<='9') {
      *n = ( *n*10UL+(uint32_t)(uint8_t)h[i])-48UL;
      ++i;
   }
   return h[i]==0;
} /* end GetNum() */


static void parms(void)
{
   char err0;
   char h[1024];
   uint32_t i;
   uint32_t fromdigiport;
   err0 = 0;
   for (;;) {
      osi_NextArg(h, 1024ul);
      if (h[0U]==0) break;
      if ((h[0U]=='-' && h[1U]) && h[2U]==0) {
         if (h[1U]=='l') {
            osi_NextArg(h, 1024ul);
            if (!GetNum(h, 1024ul, &i)) Err("-l minutes", 11ul);
            lifetime = i*60UL;
         }
         else if (h[1U]=='L') {
            osi_NextArg(h, 1024ul);
            if (!GetNum(h, 1024ul, &i)) Err("-L minutes", 11ul);
            alllifetime = i*60UL;
         }
         else if (h[1U]=='a') peertopeer = 1;
         else if (h[1U]=='I') defaultbcin = 1;
         else if (h[1U]=='O') defaultbcout = 1;
         else if (h[1U]=='b') {
            osi_NextArg(h, 1024ul);
            if (h[0U]==0) Err("-b call", 8ul);
            i = 0UL;
            if (!Str2Call(h, 1024ul, &i, 0UL, broadcastdest, 7ul)) {
               Err("-b wrong SSID", 14ul);
            }
         }
         else if (h[1U]=='i') {
            /* init filename */
            osi_NextArg(initfn, 1025ul);
         }
         else if (h[1U]=='w') {
            /* write table filename */
            osi_NextArg(wrfn, 1025ul);
         }
         else if (h[1U]=='m') {
            osi_NextArg(h, 1024ul);
            if (!GetNum(h, 1024ul, &maxentries)) Err("-m number", 10ul);
         }
         else if (h[1U]=='p') {
            osi_NextArg(h, 1024ul);
            if (!GetNum(h, 1024ul, &touserport)) Err("-p portnumber", 14ul);
         }
         else if (h[1U]=='u') {
            osi_NextArg(h, 1024ul);
            if (GetIp(h, 1024ul, &digiip, &todigiport, &fromdigiport,
                &digisock, &checkdigiip)<0L) {
               Err("cannot open digi udp socket", 28ul);
            }
         }
         else if (h[1U]=='v') show = 1;
         else if (h[1U]=='V') {
            show = 1;
            noinf = 0;
         }
         else {
            if (h[1U]=='h') {
               osic_WrLn();
               osi_WrStrLn(" -a                                route user-to-\
digi AND user-to-user", 71ul);
               osi_WrStrLn(" -b <call>                         broadcast dest\
ination call", 62ul);
               osi_WrStrLn(" -h                                this", 40ul);
               osi_WrStrLn(" -I                                for new user: \
broadcast INPUT on", 68ul);
               osi_WrStrLn(" -i <file>                         init routes fr\
om file", 57ul);
               osi_WrStrLn(" -L <time>                         minutes route \
to all ssid\'s (default 10 min)", 80ul);
               osi_WrStrLn("                                   0 no all ssid \
routing", 57ul);
               osi_WrStrLn(" -l <time>                         minutes lifeti\
me (default 1 week)", 69ul);
               osi_WrStrLn(" -m <maxentries>                   else delete ol\
d entries (default 1000)", 74ul);
               osi_WrStrLn(" -O                                for new user: \
broadcast OUTPUT on", 69ul);
               osi_WrStrLn(" -p <userport>                     udp port for u\
sers", 54ul);
               osi_WrStrLn(" -u <x.x.x.x:destport:listenport>  axudp to digi \
/listenport check ip", 70ul);
               osi_WrStrLn(" -v                                verbous",
                43ul);
               osi_WrStrLn(" -V                                verbous + fram\
es with Text", 62ul);
               osi_WrStrLn(" -w <file>                         write user tab\
le to file (only if new entries and max. every 15s)", 101ul);
               osic_WrLn();
               osi_WrStrLn("Initfile:", 10ul);
               osi_WrStrLn("NOCALL-15 192.168.0.1:4711 #comment", 36ul);
               osi_WrStrLn("NOCALL-15 p 192.168.0.1:4711 #protected entry",
                46ul);
               osi_WrStrLn("#comment", 9ul);
               osi_WrStrLn("NOCALL-15 p 0.0.0.0:0 no data to this call except\
 to digi port", 63ul);
               osi_WrStrLn("b enable broadcast input", 25ul);
               osi_WrStrLn("B enable broadcast output", 26ul);
               osi_WrStrLn("A send all frames", 18ul);
               osic_WrLn();
               osi_WrStrLn("Routing Table:", 15ul);
               osi_WrStrLn("(-l) time old table entries will be purged except\
 those from init file", 71ul);
               osic_WrLn();
               osi_WrStrLn("Source: AX.25 Source Call makes table entry with \
call/ip/sourceport/date", 73ul);
               osi_WrStrLn("        Exception: Protected entry updates Date o\
nly", 53ul);
               osic_WrLn();
               osi_WrStrLn("Destinationcall: (call used for routing)", 41ul);
               osi_WrStrLn("  First Digi with no H-bit, if not present, ax25 \
destination call is used", 74ul);
               osic_WrLn();
               osi_WrStrLn("Broadcast: if user enabled to input broadcast and\
 ax25-destination equals broadcast call", 89ul);
               osic_WrLn();
               osi_WrStrLn("Destination: axudp ip/port", 27ul);
               osic_WrLn();
               osi_WrStrLn("Routing: frame will be sent to if", 34ul);
               osi_WrStrLn("  user enabled to get all data", 31ul);
               osi_WrStrLn("  OR broadcast and user enabled to get broadcast",
                 49ul);
               osi_WrStrLn("  OR destinationcall with ssid equals user",
                43ul);
               osi_WrStrLn("  OR destinationcall with ssid fits to no user in\
 table", 56ul);
               osi_WrStrLn("     but destinationcall without ssid equals user\
 seen since -L time", 69ul);
               osi_WrStrLn("  exception 1: data never sent (back) to ip/port \
where came from", 65ul);
               osi_WrStrLn("  exception 2: data sent only one time to ip/port\
 even if more destinationcalls share same ip/port", 99ul);
               osi_WrStrLn("  exception 3: with not \'-a\' only data from dig\
i port routes to destinationcall", 80ul);
               osic_WrLn();
               X2C_ABORT();
            }
            err0 = 1;
         }
      }
      else {
         /*
               h[0]:=0C;
         */
         err0 = 1;
      }
      if (err0) break;
   }
   if (err0) {
      osi_WrStr(">", 2ul);
      osi_WrStr(h, 1024ul);
      osi_WrStrLn("< use -h", 9ul);
      X2C_ABORT();
   }
} /* end parms() */


static void ip2str(uint32_t ip, uint32_t port, char s[],
                uint32_t s_len)
{
   char h[21];
   s[0UL] = 0;
   aprsstr_IntToStr((int32_t)(ip/16777216UL), 1UL, h, 21ul);
   aprsstr_Append(s, s_len, h, 21ul);
   aprsstr_Append(s, s_len, ".", 2ul);
   aprsstr_IntToStr((int32_t)(ip/65536UL&255UL), 1UL, h, 21ul);
   aprsstr_Append(s, s_len, h, 21ul);
   aprsstr_Append(s, s_len, ".", 2ul);
   aprsstr_IntToStr((int32_t)(ip/256UL&255UL), 1UL, h, 21ul);
   aprsstr_Append(s, s_len, h, 21ul);
   aprsstr_Append(s, s_len, ".", 2ul);
   aprsstr_IntToStr((int32_t)(ip&255UL), 1UL, h, 21ul);
   aprsstr_Append(s, s_len, h, 21ul);
   aprsstr_Append(s, s_len, ":", 2ul);
   aprsstr_IntToStr((int32_t)port, 1UL, h, 21ul);
   aprsstr_Append(s, s_len, h, 21ul);
} /* end ip2str() */


static void showpip(uint32_t ip, uint32_t port)
{
   char h[51];
   ip2str(ip, port, h, 51ul);
   osi_WrStr(h, 51ul);
} /* end showpip() */


static void showcall(const char b[], uint32_t b_len, uint32_t start)
{
   char h[16];
   uint32_t l;
   l = 0UL;
   if (Call2Str(b, b_len, h, 16ul, start, &l)) {
      h[l] = 0;
      osi_WrStr(h, 16ul);
   }
} /* end showcall() */


static void listtab(char fn[], uint32_t fn_len)
{
   int32_t fd;
   pUSER u;
   char s[201];
   char h[201];
   uint32_t j;
   uint32_t i;
   X2C_PCOPY((void **)&fn,fn_len);
   fd = osi_OpenWrite(fn, fn_len);
   if (osic_FdValid(fd)) {
      u = users;
      while (u) {
         i = 0UL;
         if (Call2Str(u->call, 7ul, h, 201ul, 0UL, &i)) {
            while (i<10UL) {
               h[i] = ' ';
               ++i;
            }
            h[i] = 0;
         }
         else h[0U] = 0;
         j = 0UL;
         if (u->nospoof) {
            aprsstr_Append(h, 201ul, "p", 2ul);
            ++j;
         }
         else if (u->nopurge) {
            aprsstr_Append(h, 201ul, "f", 2ul);
            ++j;
         }
         if (u->bcout) {
            aprsstr_Append(h, 201ul, "B", 2ul);
            ++j;
         }
         if (u->bcin) {
            aprsstr_Append(h, 201ul, "b", 2ul);
            ++j;
         }
         if (u->willall) {
            aprsstr_Append(h, 201ul, "A", 2ul);
            ++j;
         }
         while (j<4UL) {
            aprsstr_Append(h, 201ul, " ", 2ul);
            ++j;
         }
         ip2str(u->uip, u->dport, s, 201ul);
         aprsstr_Append(h, 201ul, s, 201ul);
         if (u->htime>0UL) {
            i = aprsstr_Length(h, 201ul);
            while (i<36UL) {
               h[i] = ' ';
               ++i;
            }
            h[i] = 0;
            aprsstr_DateToStr(u->htime, s, 201ul);
            aprsstr_Append(h, 201ul, s, 201ul);
         }
         aprsstr_Append(h, 201ul, "\012", 2ul);
         osi_WrBin(fd, (char *)h, 201u/1u, aprsstr_Length(h, 201ul));
         u = u->next;
      }
      strncpy(h,"f from init file, \012p ip/port protected\012B BC out\012b B\
C in\012A gets all\012",201u);
      osi_WrBin(fd, (char *)h, 201u/1u, aprsstr_Length(h, 201ul));
      if (peertopeer) {
         strncpy(h,"peer-to-peer routing on\012",201u);
         osi_WrBin(fd, (char *)h, 201u/1u, aprsstr_Length(h, 201ul));
      }
      i = 0UL;
      if (Call2Str(broadcastdest, 7ul, h, 201ul, 0UL, &i)) {
         h[i] = 0;
         aprsstr_Append(h, 201ul, " broadcast destination\012", 24ul);
         osi_WrBin(fd, (char *)h, 201u/1u, aprsstr_Length(h, 201ul));
      }
      aprsstr_IntToStr((int32_t)touserport, 0UL, h, 201ul);
      aprsstr_Append(h, 201ul, " user UDP port\012", 16ul);
      osi_WrBin(fd, (char *)h, 201u/1u, aprsstr_Length(h, 201ul));
      osic_Close(fd);
   }
   else Err("-w File Create", 15ul);
   X2C_PFREE(fn);
} /* end listtab() */


static char cmpcall(const RAWCALL c, const char b[],
                uint32_t b_len, uint32_t start, char withssid)
{
   uint32_t i;
   for (i = 0UL; i<=5UL; i++) {
      if (c[i]!=b[i+start]) return 0;
   } /* end for */
   return !withssid || ((uint32_t)(uint8_t)c[6U]/2UL&15UL)==((uint32_t)
                (uint8_t)b[start+6UL]/2UL&15UL);
} /* end cmpcall() */


static pUSER Realloc(char alloc)
{
   uint32_t cnt;
   pUSER new0;
   pUSER last;
   pUSER u;
   cnt = 0UL;
   last = 0;
   new0 = 0;
   u = users;
   while (u) {
      if (!u->nopurge && (u->htime+lifetime<systime || cnt>=maxentries)) {
         /* old or too much entries */
         if (last==0) users = u->next;
         else last->next = u->next;
         if (show) {
            osi_WrStr("Purge User ", 12ul);
            showcall(u->call, 7ul, 0UL);
            osic_WrLn();
         }
         if (alloc && new0==0) new0 = u;
         else osic_free((char * *) &u, sizeof(struct USER));
         if (last==0) u = users;
         else u = last->next;
         modified = 1;
      }
      else {
         last = u;
         u = u->next;
         ++cnt;
      }
   }
   if (alloc && new0==0) {
      osic_alloc((char * *) &new0, sizeof(struct USER));
   }
   /*  IF show THEN WrStr(" Table entries="); WrInt(cnt, 1); WrLn; END; */
   return new0;
} /* end Realloc() */


static void showu(uint32_t dp, pUSER u)
{
   if (u->nospoof) osi_WrStr(" writeprotected", 16ul);
   if (u->bcout) osi_WrStr(" bc-out", 8ul);
   if (u->bcin) osi_WrStr(" bc-in", 7ul);
   if (u->willall) osi_WrStr(" gets-all", 10ul);
   osi_WrStr(" IP:", 5ul);
   showpip(u->uip, dp);
   osic_WrLn();
} /* end showu() */


static void AddIp(uint32_t ip, uint32_t dp, char fix,
                char nspoof, char * hasbcin,
                char defbcin, char defbcout,
                char getsall, const char buf[],
                uint32_t buf_len)
{
   pUSER last;
   uint32_t i;
   pUSER u;
   /* for fast find, rechain to first position */
   struct USER * anonym;
   struct USER * anonym0;
   *hasbcin = 0;
   if (!((uint32_t)(uint8_t)buf[13UL]&1) && (uint8_t)
                buf[20UL]>=(uint8_t)'\200') return;
   /* via digi, store only direct heard */
   u = users;
   last = 0;
   while (u) {
      if (cmpcall(u->call, buf, buf_len, 7UL, 1)) {
         if (last) {
            last->next = u->next;
            u->next = users;
            users = u;
         }
         { /* with */
            struct USER * anonym = u;
            anonym->htime = systime;
            *hasbcin = anonym->bcin;
            if (!anonym->nospoof) {
               anonym->uip = ip; /* store if ip changed */
               anonym->dport = dp;
            }
         }
         /*
               IF show THEN
                 WrStr("Found User "); showcall(buf, SOURCECALL);
                 showu(u);
               END;
         */
         return;
      }
      last = u;
      u = u->next;
   }
   if (u==0) {
      u = Realloc(1);
      if (u==0) {
         if (show) osi_WrStrLn(" user add out of memory", 24ul);
      }
      else {
         { /* with */
            struct USER * anonym0 = u;
            for (i = 0UL; i<=6UL; i++) {
               anonym0->call[i] = buf[7UL+i];
            } /* end for */
            anonym0->call[6U] = (char)(((uint32_t)(uint8_t)
                anonym0->call[6U]/2UL&15UL)*2UL); /* extract pure ssid */
            anonym0->uip = ip;
            anonym0->dport = dp;
            anonym0->htime = systime;
            anonym0->nospoof = nspoof;
            anonym0->nopurge = fix;
            anonym0->bcin = defbcin;
            anonym0->bcout = defbcout;
            anonym0->willall = getsall;
         }
         *hasbcin = defbcin;
         if (show) {
            osi_WrStr("Add User ", 10ul);
            showcall(buf, buf_len, 7UL);
            showu(dp, u);
         }
         u->next = users;
         users = u;
      }
   }
} /* end AddIp() */


static char sendtouser(char ubuf0[], uint32_t ubuf_len,
                int32_t blen0, char fromdigi, char bcin,
                uint32_t fromip0, uint32_t fromport)
{
   pUSER uu;
   pUSER u;
   pUSER exactu;
   int32_t ci;
   int32_t res0;
   char ok0;
   char broadcast;
   char topeer;
   ok0 = 0;
   ci = 7L;
   if (((uint32_t)(uint8_t)ubuf0[13UL]&1)) ci = 0L;
   else {
      /* find first not done via */
      for (;;) {
         ci += 7L;
         if (ci>63L || ci+7L>=blen0) return 0;
         /* no valid frame */
         if ((uint8_t)ubuf0[ci+6L]<(uint8_t)'\200') break;
         if (((uint32_t)(uint8_t)ubuf0[ci+6L]&1)) {
            ci = 0L; /* all h-bits set */
            break;
         }
      }
   }
   /*WrInt(ci, 1); WrStrLn(" =ci"); */
   broadcast = (((fromdigi || bcin) && ci==0L) && broadcastdest[0U])
                && cmpcall(broadcastdest, ubuf0, ubuf_len, 0UL, 1);
   topeer = !broadcast && (peertopeer || fromdigi); /* data for an user */
   if (topeer) {
      exactu = users;
      while (exactu && !cmpcall(exactu->call, ubuf0, ubuf_len,
                (uint32_t)ci, 1)) {
         /* conpare with ssid */
         exactu = exactu->next; /* find call + ssid match */
      }
   }
   else exactu = 0;
   /*WrInt(ORD(exactu<>NIL), 1); WrStrLn(" =exactu"); */
   u = users;
   while (u) {
      u->datagot = 0;
      if ((u->dport>0UL && ((fromdigi || fromip0!=u->uip)
                || fromport!=u->dport)) && (((u->willall || u==exactu)
                || broadcast && u->bcout) || (topeer && exactu==0)
                && cmpcall(u->call, ubuf0, ubuf_len, (uint32_t)ci, 0))) {
         /* user enabled */
         /* send never same way back */
         /* try all same call without ssid match */
         uu = users;
         while (uu!=u && ((!uu->datagot || uu->uip!=u->uip)
                || uu->dport!=u->dport)) uu = uu->next;
         if (uu==u) {
            /* have not sent jet to ip/port */
            u->datagot = 1;
            res0 = udpsend(usersock, ubuf0, blen0, u->dport, u->uip);
            if (show) {
               osi_WrStr("< send to user ", 16ul);
               showcall(u->call, 7ul, 0UL);
               osi_WrStr(" ", 2ul);
               showpip(u->uip, u->dport);
               osi_WrStrLn("", 1ul);
            }
            if (u==exactu || broadcast) ok0 = 1;
         }
      }
      /*
      ELSE
      showcall(u^.call, 0); WrStrLn(" sendno");
      WrInt(ORD(u=exactu), 1); WrStr(" =u=exactu ");
      showpip(u^.uip, u^.dport); WrStr(" =userip ");
      showpip(fromip, fromport); WrStr(" =fromip ");
      */
      u = u->next;
   }
   /*  IF show & ok THEN WrStrLn("") END; */
   return ok0;
} /* end sendtouser() */


static void err(char h[], uint32_t h_len, char fn[],
                uint32_t fn_len, uint32_t lc)
{
   char s[4001];
   X2C_PCOPY((void **)&h,h_len);
   X2C_PCOPY((void **)&fn,fn_len);
   osi_WrStr("Error in line ", 15ul);
   aprsstr_IntToStr((int32_t)lc, 1UL, s, 4001ul);
   aprsstr_Append(s, 4001ul, ":[", 3ul);
   aprsstr_Append(s, 4001ul, fn, fn_len);
   aprsstr_Append(s, 4001ul, "] ", 3ul);
   aprsstr_Append(s, 4001ul, h, h_len);
   osi_WrStrLn(s, 4001ul);
   X2C_PFREE(h);
   X2C_PFREE(fn);
} /* end err() */


static void initroutes(char fn[], uint32_t fn_len)
{
   int32_t fd;
   char call[201];
   char b[201];
   uint32_t lc;
   uint32_t j;
   uint32_t i;
   char dbcin0;
   char all;
   char bci;
   char bco;
   char spoof;
   uint32_t ip;
   uint32_t dp;
   X2C_PCOPY((void **)&fn,fn_len);
   fd = osi_OpenRead(fn, fn_len);
   if (osic_FdValid(fd)) {
      lc = 1UL;
      for (;;) {
         i = 0UL;
         do {
            if (osi_RdBin(fd, (char *) &b[i], 1u/1u, 1UL)<1L) {
               goto loop_exit;
            }
            ++i;
         } while (!(i>=200UL || b[i-1UL]=='\012'));
         b[i] = 0;
         if (b[0U]!='#') {
            i = 0UL;
            if (!Str2Call(b, 201ul, &i, 7UL, call, 201ul)) {
               err("wrong SSID in Init File", 24ul, fn, fn_len, lc);
            }
            while (b[i]==' ') ++i;
            spoof = b[i]=='p';
            if (spoof) ++i;
            all = b[i]=='A';
            if (all) ++i;
            bci = b[i]=='b';
            if (bci) ++i;
            bco = b[i]=='B';
            if (bco) ++i;
            while (b[i]==' ') ++i;
            if (all && bco) {
               err("Broadcast out AND get-all? in Init File", 40ul, fn,
                fn_len, lc);
            }
            j = 0UL;
            while ((uint8_t)b[i]>' ' && b[i]!='#') {
               b[j] = b[i];
               ++j;
               ++i;
            }
            b[j] = 0;
            if (GetIp1(b, 201ul, &ip, &dp)<0L) {
               err("wrong IP:PORT in Init File", 27ul, fn, fn_len, lc);
            }
            else {
               AddIp(ip, dp, 1, spoof, &dbcin0, bci, bco, all, call, 201ul);
            }
         }
         ++lc;
      }
      loop_exit:;
      osic_Close(fd);
   }
   else Err("-i File not found", 18ul);
   X2C_PFREE(fn);
} /* end initroutes() */

static char ubuf[338];

static int32_t blen;

static int32_t res;

static uint32_t fromip;

static uint32_t userdport;

static uint32_t lastlist;

static char dbcin;

static pUSER voidu;


X2C_STACK_LIMIT(100000l)
extern int main(int argc, char **argv)
{
   X2C_BEGIN(&argc,argv,1,4000000l,8000000l);
   if (sizeof(RAWCALL)!=7) X2C_ASSERT(0);
   aprsstr_BEGIN();
   osi_BEGIN();
   Gencrctab();
   noinf = 1;
   show = 0;
   peertopeer = 0;
   defaultbcin = 0;
   defaultbcout = 0;
   touserport = 0UL;
   digisock = -1L;
   maxentries = 1000UL;
   lifetime = 604800UL;
   alllifetime = 600UL;
   users = 0;
   initfn[0U] = 0;
   wrfn[0U] = 0;
   broadcastdest[0U] = 0;
   parms();
   if (initfn[0U]) initroutes(initfn, 1025ul);
   modified = 1;
   if (!peertopeer && digisock<0L) Err("need -u parameter", 18ul);
   usersock = openudp();
   if ((touserport==0UL || usersock<0L) || bindudp(usersock, touserport)<0L) {
      Err("cannot bind userport (-p userport)", 35ul);
   }
   systime = osic_time();
   for (;;) {
      fdclr();
      if (digisock>=0L) fdsetr((uint32_t)digisock);
      fdsetr((uint32_t)usersock);
      if (selectrw(15UL, 0UL)>0L) {
         if (digisock>=0L && issetr((uint32_t)digisock)) {
            /* data from digi */
            fromip = digiip;
            blen = getudp(digisock, ubuf, 338ul, &fromip, &userdport,
                checkdigiip);
            if (blen>=17L) {
               if (show) {
                  osi_WrStr("> from digi", 12ul);
                  ShowFrame(ubuf, 338ul, (uint32_t)(blen-2L), noinf);
               }
               if (!sendtouser(ubuf, 338ul, blen, 1, 1, digiip,
                userdport) && show) {
                  osi_WrStrLn(" digi out user not found", 25ul);
               }
               if (show) osi_WrStrLn("-----", 6ul);
            }
         }
         if (issetr((uint32_t)usersock)) {
            /* data from user */
            fromip = 0UL;
            blen = getudp(usersock, ubuf, 338ul, &fromip, &userdport, 0);
            if (blen>=17L) {
               if (show) {
                  osi_WrStr("> from user ", 13ul);
                  showpip(fromip, userdport);
                  ShowFrame(ubuf, 338ul, (uint32_t)(blen-2L), noinf);
               }
               AddIp(fromip, userdport, 0, 0, &dbcin, defaultbcin,
                defaultbcout, 0, ubuf, 338ul);
               modified = 1;
               if (digisock>=0L) {
                  res = udpsend(digisock, ubuf, blen, todigiport, digiip);
               }
               if (!sendtouser(ubuf, 338ul, blen, 0, dbcin, fromip,
                userdport) && show) {
                  osi_WrStrLn(" peer-to-peer no user found", 28ul);
               }
               if (show) osi_WrStrLn("-----", 6ul);
            }
         }
      }
      systime = osic_time();
      if (lastlist+15UL<systime || lastlist>systime) {
         voidu = Realloc(0); /* cyclic purge */
         if (modified && wrfn[0U]) {
            listtab(wrfn, 1025ul);
            modified = 0;
         }
         lastlist = systime;
      }
   }
   X2C_EXIT();
   return 0;
}

X2C_MAIN_DEFINITION
