/*
 * SMS4IoT HA integration sms4iot gateway
 * Copyright notice:
 * SPDX-FileCopyrightText: 2026 Copyright Department C Incorporated
 * SPDX-License-Identifier: CC-BY-NC-4.0 https://creativecommons.org/licenses/by-nc/4.0/legalcode.en
 *
 * The above copyright notice shall be included in all copies or substantial portions of this software.
 * Note:sms4iot protocol and associated gateway architecture patented US11109197
 * Hey! Even a few poor developers deserve something for our work. Contact us for reasonable commercial licensing terms.
 *
 * cc -static s4igw.c -o s4igw -lpthread
 *
 * based on ~/s4i3imqtt/tmp/s4i3imqtt.c
 * 2021 Copyright ZX Communications, Inc  All rights reserved
 * 2022-2025 Copyright Department C Inc. All rights reserved
 * sms4iot to mqtt
 * tossed together by RHLamb Jun 2022
 * updated to ASCON crypto July 2025
 * mqttsrvr<------->this<-udp->itdtlondonsrvr<->"the dns"<->sms4iotdevice
 * your responsibitiy | <-s4i encrypted-------------------------------->
 *                 sms4iotdevice keys
 */
#define VERSION "V0.007 s4igw (c) 2026 Department C Inc. All rights reserved" 
#define S4ICONFSERVER_DEFAULT "ns.5.x031.nl"
#define S4ICONFSERVERPORT_DEFAULT 54391
#define S4ICONFSERVER_SPACING_DEFAULT 6
#define LOGFILE_MAXSIZE_DEFAULT 5000000
#define LOGFILE_MAXNUMBER_DEFAULT 3

#include <sys/types.h>
#include <signal.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <stdarg.h>
#include <errno.h>
#include <pthread.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <dirent.h>

#pragma GCC diagnostic ignored "-Wformat-overflow"

#define DATA_BUFSIZE 8192
#define NARGS 20

static int debug=1; // 0:minimal 1:typical 2:lots

static char CURDIR[MAXPATHLEN];
static char MYCONFFILE[MAXPATHLEN];
static char LOGFILE[MAXPATHLEN];
static int LOGFILE_MAXSIZE=LOGFILE_MAXSIZE_DEFAULT;
static int LOGFILE_MAXNUMBER=LOGFILE_MAXNUMBER_DEFAULT;
static char MYDEVDIR[MAXPATHLEN];
static char MYDATADIR[MAXPATHLEN];
static char S4ICONFSERVER[256];
static int S4ICONFSERVERPORT=S4ICONFSERVERPORT_DEFAULT;
static int S4ICONFSERVER_SPACING=S4ICONFSERVER_SPACING_DEFAULT;

static FILE *flog=NULL;
static void _logit(const char *f,const char *format, ...);
#define mylog(...) _logit(__func__,__VA_ARGS__)

#define max(a,b) (((a) > (b)) ? (a) : (b))
#define min(x,y) ((x)<(y)?(x):(y))
#define PTR232BIT(a) (uint32_t)(  ((uint32_t)(a)[0]<<24) | ((a)[1]<<16) | ((a)[2]<<8) | (a)[3] )
#define PTR232BITHOST(a) (uint32_t)(  ((uint32_t)(a)[3]<<24) | ((a)[2]<<16) | ((a)[1]<<8) | (a)[0] )
#define PTR216BIT(a) (uint16_t)(((a)[0]<<8)|(a)[1])
#define PTR216BITHOST(a) (uint16_t)(((a)[1]<<8)|(a)[0])
#define SET16BIT(a,b) { (a)[0] = b&0xFF; (a)[1] = (b>>8); }

static pthread_mutex_t mem_mutex=PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t devid_mutex=PTHREAD_MUTEX_INITIALIZER;
static int devid_mutex_inuse=0;
static pthread_mutex_t rlprintf_mutex=PTHREAD_MUTEX_INITIALIZER;
static uint mutexwait=0,mutexcnt=0;
static int _mymutex(pthread_mutex_t *t,const char *fnc,uint8_t sr)
{
  if(sr) {
    //printf("%s lock\n",fnc);
    mutexcnt++;
    if(pthread_mutex_trylock(t)) { // failed
      mutexwait++;
      if(pthread_mutex_lock(t)) { // wait for lock
        mylog("error %s-LOCK: %s\n",fnc,strerror(errno));
        return -1;
      }
    }
  } else {
    //printf("%s unlock\n",fnc);
    if(pthread_mutex_unlock(t)) {
      mylog("error %s-UNLOCK: %s\n",fnc,strerror(errno));
      return -1;
    }
  }
  return 0;
}
#define mymutex(a,b) _mymutex(a,__func__,b)

//#define TESTMYMALLOC
#ifdef TESTMYMALLOC
static char *mymemslots[32000];
#endif // TESTMYMALLOC
static uint32_t memmax=0,memtot=0;
static void *mymalloc(int n)
{
  uint8_t *p;

  if((p=(uint8_t *)malloc(n+4)) == NULL) {
    mylog("error: Failed malloc(%d) memtot:%u - just exit\n",n,memtot);
    exit(-1);
  }
  *(uint32_t *)p = n;

//mylog("%d %p\n",n,p);

  mymutex(&mem_mutex,1);
  memtot += n;
  if(memtot > memmax) memmax = memtot;
  mymutex(&mem_mutex,0);
#ifdef TESTMYMALLOC
  {
    int i;
    for(i=0;i<32000;i++) if(mymemslots[i] == 0) break;
    if(i < 32000) mymemslots[i] = (p+4);
  }
#endif // TESTMYMALLOC
  return (void *)(p+4);
}
static void myfree(void *q)
{
  uint32_t n;
  uint8_t *p;

  if((p = (uint8_t *)q) == NULL) {
    mylog("error: myfree failed: NULL argument\r\n");
    return;
  }
#ifdef TESTMYMALLOC
  {
    int i;
    for(i=0;i<32000;i++) if(mymemslots[i] == (char *)p) break;
    if(i < 32000) mymemslots[i] = NULL;
  }
#endif // TESTMYMALLOC
  p -= 4;
  n = *(uint32_t *)p;

  if(n == 0) mylog("error myfree %d %p\n",n,p);

  mymutex(&mem_mutex,1);
  memtot -= n;
  mymutex(&mem_mutex,0);

#ifdef TESTMYMALLOC
  memset(p,0,n+4);
#endif // TESTMYMALLOC
  
  memset(p,0,n+4);
  
  free(p);
}
static void mymemstat(void)
{
  mylog("memtot/memmax:%d/%d\n",memtot,memmax);
#ifdef TESTMYMALLOC
  {
    int i;
    for(i=0;i<32000;i++) {
      if(mymemslots[i]) {
        mylog("unfreed %d bytes p=%p\n",*(uint32_t *)(mymemslots[i]-4),mymemslots[i]);
      }
    }
  }
#endif // TESTMYMALLOC 
}

static char *mystrdup(char *str)
{
  int i;
  char *p;

  if(str == NULL) return (char *)0;
  i = strlen(str);
  p = (char *)mymalloc(i+1);
  if(p == NULL) return (char *)0;
  strcpy(p,str);
  return p;
}
static uint8_t *mymemdup(uint8_t *p,int n)
{
  uint8_t *q;

//mylog("%p %d\n",p,n);

  q = (uint8_t *)mymalloc(n+1);
  memcpy(q,p,n);
  q[n] = '\0'; // just in case
  return q;
}

static void _logit(const char *f,const char *format, ...)
{
  va_list args;
  
  if(mymutex(&rlprintf_mutex,1)) {
    printf("%s: inuse from %s. Dropping.\n",__func__,f?f:"NULL");
    return;
  }  
  {
    fprintf(stdout,"%s:",f);
    va_start(args,format);
    vfprintf(stdout,format,args);
    va_end(args);
    fflush(stdout);
  }
  if(flog) {
    fprintf(flog,"%s:",f);
    va_start(args,format);
    vfprintf(flog,format,args);
    va_end(args);
    fflush(flog);
  }
  mymutex(&rlprintf_mutex,0);
}
static void logfile_rollover(void)
{
  char buf[MAXPATHLEN],nowstr[50];
  struct stat st;
  if(flog == NULL) return;
  if(stat(LOGFILE,&st) == 0 && st.st_size > LOGFILE_MAXSIZE) {
    time_t ltmp;
    fclose(flog);
    time(&ltmp);
    strftime(nowstr,sizeof(nowstr),"%Y%m%d%H%M%S",gmtime(&ltmp));
    sprintf(buf,"%s.%s",LOGFILE,nowstr);
    rename(LOGFILE,buf);
    flog = fopen(LOGFILE,"a");
  }
  // prune old logfiles
  {
    DIR *dirp;
    struct dirent *dp;
    char *scandir,*p,oldest[MAXPATHLEN];
    int n,k;
    scandir = CURDIR;
    if((p=strrchr(LOGFILE,'/')) == NULL) return;
    p++;
    n = strlen(p);
    sprintf(oldest,"%s.30220101000000",p);
    k = 0;
    if((dirp = opendir(scandir)) == (DIR *)0) {
      mylog("Cannot open directory %s\n",scandir);
      return;
    }
    while((dp=readdir(dirp))) {
      if(strcmp(dp->d_name,".") == 0 || strcmp(dp->d_name,"..") == 0) continue;
      if(strncmp(dp->d_name,p,n)) continue;
      if(debug>1) mylog("Found |%s|\n",dp->d_name);
      if(strlen(dp->d_name) == n) continue;
      k++;
      if(strcmp(dp->d_name,oldest) < 0) strcpy(oldest,dp->d_name);
    }
    closedir(dirp);
    if(k > LOGFILE_MAXNUMBER) {
      sprintf(buf,"%s/%s",CURDIR,oldest);
      mylog("Removing |%s|\n",buf);
      unlink(buf);
    }
  }
}

static uint8_t startshutdown=0;
static void mysighandler(int sig) {
  mylog("signal = %d\n",sig);
  startshutdown = 1;
  return; //exit(0);
}
static void blocksignals(int flg)
{
  if(flg) {
    signal(SIGCHLD,mysighandler);
    signal(SIGHUP,mysighandler);
    signal(SIGINT,mysighandler);
    signal(SIGQUIT,mysighandler);
    signal(SIGTERM,mysighandler);
    signal(SIGKILL,mysighandler); // cant block this one
    return;
  }
  signal(SIGCHLD,SIG_DFL);
  signal(SIGHUP,SIG_DFL);
  signal(SIGINT,SIG_DFL);
  signal(SIGQUIT,SIG_DFL);
  signal(SIGTERM,SIG_DFL);
  signal(SIGKILL,SIG_DFL);
}

#define MYDNSSTATIC
#ifdef MYDNSSTATIC
static int mydnslookup(uint8_t *out,int olen,char *str);
static char *lookup_v4host(char *host)
{
  static char addrstr[128];
  uint8_t out[32];
  if(host == NULL) return NULL;
  int n = mydnslookup(out,sizeof(out),host);
  if(n == 4) sprintf(addrstr,"%u.%u.%u.%u\n",out[0],out[1],out[2],out[3]);
  else if(n == 16) {
    sprintf(addrstr,"%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x\n",
             out[0],out[1],out[2],out[3],out[4],out[5],out[6],out[7],
             out[8],out[9],out[10],out[11],out[12],out[13],out[14],out[15]);
  } else return NULL;
  return addrstr;
}
#else // MYDNSSTATIC
static char *lookup_v4host(char *host)
{
  struct addrinfo hints,*res,*result;
  int err;
  static char addrstr[100];
  void *ptr;
  if(host == NULL) return NULL;
  
  memset(&hints,0,sizeof (hints));
  hints.ai_family = PF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags |= AI_CANONNAME;  
  if((err=getaddrinfo(host,NULL,&hints,&result))) {
    mylog("getaddrinfo %s failed %s\n",host,gai_strerror(err));
    return NULL;
  }
  res = result;
  addrstr[0] = '\0';
  // pick v4 only for now
  while(res) {
    //inet_ntop(res->ai_family,res->ai_addr->sa_data,addrstr,sizeof(addrstr));
    switch(res->ai_family) {
    case AF_INET:
      ptr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
      inet_ntop(res->ai_family,ptr,addrstr,sizeof(addrstr));
      break;
    case AF_INET6:
      ptr = &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
      //inet_ntop(res->ai_family,ptr,addrstr,sizeof(addrstr));
      break;
    }
    //inet_ntop(res->ai_family,ptr,addrstr,sizeof(addrstr)); 
    //if(debug>1) mylog("IPv%d address: %s (%s)\n",res->ai_family == PF_INET6?6:4,addrstr,res->ai_canonname);
    res = res->ai_next;
  }
  freeaddrinfo(result);
  if(addrstr[0] == '\0') return NULL; // no IPv4 address found
  return addrstr;
}
#endif // MYDNSSTATIC

static int cleanup(char *io)
{
  char *q,*p;

  /* rid trailing space and sr lf */
  for(q = io + strlen(io);q-- != io && (*q == ' ' || *q == '\t' || *q == '\r' || *q == '\n');) ;
  *(q+1) = '\0';
  /* rid leading space */
  for(q=io;*q == ' ' || *q == '\t';q++) ;
  for(p=io;*q;) *p++ = *q++;
  *p = '\0';
  return 0;
}
static int lparse(char *line,char *argv[],int maxargs,char delc)
{
  char *cp;
  int argc,qflag;

  if(line == NULL) return 0;

  if((cp = strchr(line,'\r')) != (char *)0) *cp = '\0';
  if((cp = strchr(line,'\n')) != (char *)0) *cp = '\0';

  for(argc=0;argc<maxargs;argc++) argv[argc] = (char *)0;

  for(argc=0;argc<maxargs;) {
    qflag = 0;
    while(*line == ' ' || *line == '\t') line++; /* whitespace */
    if(*line == '\0') break; /* done */
    if(*line == '"') { line++; qflag = 1; } /* quote */
    argv[argc++] = line;
    if(qflag) {                         /* quote */
      if((line = strchr(line,'"')) == (char *)0) return -1; /*error*/
      *line++ = '\0';
    } else {
      for(cp=line;*cp;cp++) {
        //if(*cp == ' ' || *cp == '\t' || *cp == delc) break;
	if(*cp == delc) break;
      }
      if(*cp) *cp++ = '\0'; /* non-zero */
      line = cp;
    }
  }
  return argc;
}
static int _rdump(unsigned char *ptr,int n,const char *fnc)
{
  int i,j1,j2; char buf[80]; static char htoas[]="0123456789ABCDEF";
  j1 = j2 = 0; /* gcc -W */
  for(i=0;i<n;i++,j1+=3,j2++) {
    if((i&0xf) == 0) {
      if(i) { buf[j2]='\0'; _logit(fnc,"%s|\n",buf); }
      j1=0; j2=51; memset(buf,' ',80); buf[50]='|';
    }
    buf[j1] = htoas[(ptr[i]&0xf0) >> 4]; buf[j1+1] = htoas[ptr[i]&0x0f];
    if(ptr[i] >= 0x20 && ptr[i] < 0x80) buf[j2]=ptr[i]; else buf[j2]='.';
  }
  buf[j2]='\0'; _logit(fnc,"%s|\n",buf);
  return 0;
}
#define rdump(a,b) _rdump(a,b,__func__)

static int hex2i(char c)
{
  if(c >= '0' && c <= '9') return (int)(c - '0');
  if(c >= 'A' && c <= 'F') return (int)((c - 'A') + 10);
  if(c >= 'a' && c <= 'f') return (int)((c - 'a') + 10);
  return -1;
}
static int ahex2bin(uint8_t *out,int olen,char *in)
{
  uint8_t *p;
  int i,j;
  p = out;
  while(1) {
    if(*in == 0) break;
    if(*(in+1) == 0) return -1; // not even # of chars                          
    i = hex2i(in[0]);
    j = hex2i(in[1]);
    if(i < 0 || j < 0) return -1; // bad chars                                  
    in += 2;
    if(olen <= 0) return -1; // not enough space                                
    *p++ = (uint8_t) ((i<<4)|j); // make sure inplace ahex 2 bin works          
    olen--;
  }
  return (int)(p - out);
}
static void tconvert(char *out,uint32_t in,uint8_t dur)
{
  if(out == NULL) return; // 2007-04-05T14:30Z
  if(dur) {
    if(in > 86400) sprintf(out,"%dday %dhr",(in/86400),(in%86400)/3600);
    else sprintf(out,"%dhr %dmin",in/3600,(in%3600)/60);
  } else {
    struct tm tm;
    time_t tmp=in;
    localtime_r(&tmp,&tm);
    strftime(out,32,"%Y-%m-%dT%H:%M:%S%z",&tm); // ISO8601
  }
}
static void pytconvert(char *out,uint32_t in)
{
  if(out == NULL) return; // 2000-12-25 05:30:30
  struct tm tm;
  time_t tmp=in;
  localtime_r(&tmp,&tm);
  strftime(out,32,"%Y-%m-%d %H:%M:%S",&tm); // python datetime.datetime
}


// to avoid dependance on -lcrypto library - which changes often. put your own aes routines here
//#include "myaes.c"
#include "ascon.c"

// V3.0 |time(4)|R(16n)|hash(time+R+k2)(12)| = len=16n + 16
// R=aes(|time(4)|IE|...|IE|,k1)
enum {  SMV3_TYPE_NOP, SMV3_TYPE_NEWDEV,SMV3_TYPE_DATA,SMV5_TYPE_DATA,ASCON_TYPE_DATA };
enum {IE_NULL,
      IE_STATUS,   // req=|| resp=|internet connected yes/no(4)|ip(4)|mask(4)|gw(4)|dns(4)|ssid(32)|0(1)|
      IE_WIFISET,  // req=|ssid(32)|0(1)|pass(32)|0(1)| resp=|ssid(32)|bssid(6)|rssi(1)|
      IE_WIFISCAN, // req=|| resp=|number(1)|current yes/no(1)|rssi(1)|ssid(32)|0(1)|
      IE_LOGINSET, // req=|pass(32)|0(1)| resp=|ok yes/no(2)|
      IE_RELAY,    // req=|onoff(1)| resp=IE_STATUS
      IE_WIFISCANSSID,
      IE_SETMSG,  // req=|msg(32)|0(1)|
      IE_SENSORSTAT, // req=|| resp=|n(2)|d1(4)|...|dn(4)|
      IE_SETMSG2,  // req=|msg(64)|0(1)|
      IE_GETNEWID, // req=|mac(6)| note:but (id,dn,k1,k2)=special auto config set
      IE_STATUS2, // |statflg(4)|bssid(6)|rssi(2)|bssid(6)|rssi(2)|bssid(6)|rssi(2)|uptime(4)|vbat(4)|curssid(32)|
      IE_KEYEXCHANGE,
      IE_STREAMMSG,
      IE_GETNEWID2,
      IE_SENSORDATA,      
      IE_LAST // placeholder check    
};
#define DEV_RELAY_ON       0x00000001
#define DEV_INPIN_HIGH     0x00000002
#define DEV_BSSID_HISTORY  0x00000004
#define DEV_BUTTON_PUSHED  0x00000008

#define SMS4IOT_FRESHENESS 15 // if end-2-end age of response is less, then ok

typedef struct _devidcb {
  struct _devidcb *next;
  uint8_t inuse;
  uint8_t dflg;
  char *id;
  char *swver;
  uint32_t protoversion;
  uint8_t *v3_k2;
  uint8_t *v3_k1;
  char *v3_dmsg; // when v3_msg is null
  char *v3_msg; // current msg being sent
  char *nmsg; // queue of 1
  char *nmsg0,*nmsg1,msgfile[MAXPATHLEN];
  uint32_t ttime; // last tx time
  uint8_t txseq;
  uint32_t state;
  uint8_t *out;
  int n;
  int nk2time;
  int nk1time;
  int sendcnt;

  uint32_t lasttx,rtt;
  uint8_t rxack;
  uint8_t ack;
  uint32_t lastseen_uuid;
  uint32_t lastseen_time;
  
  int wroteconfig;

  char *hadata;
} devidcb;
static devidcb *dc0=NULL;
static devidcb *devid_find(char *devid,int create)
{
  devidcb *dc;

  if(devid && strlen(devid) > 20) return NULL;
  if(devid_mutex_inuse) return NULL;
 
  mymutex(&devid_mutex,1);
  devid_mutex_inuse = 1;
  for(dc=dc0;dc;dc=dc->next) {
    if(strcmp(dc->id,devid) == 0 && dc->dflg == 0) {
      mymutex(&devid_mutex,0);
      devid_mutex_inuse = 0;
      return dc;
    }
  }
  if(create == 0) {
    mymutex(&devid_mutex,0);
    devid_mutex_inuse = 0;
    return NULL;
  }
  dc = (devidcb *)mymalloc(sizeof(devidcb));
  memset(dc,0,sizeof(devidcb));
  dc->id = mystrdup(devid);
  
  dc->next = dc0;
  dc0 = dc;
  mymutex(&devid_mutex,0);
  devid_mutex_inuse = 0;
  return dc;
}

// update_devid_entry(dc->id,"temperature",llbuf);
static void update_devid_entry(devidcb *dc,char *name,char *data)
{
  if(name == NULL) {
    if(dc->hadata) {
      myfree(dc->hadata);
      dc->hadata = NULL;
    }
    return;
  }
  if(dc->hadata == NULL) {
    dc->hadata = mymalloc(2048);
    memset(dc->hadata,0,2048);
  }
  int i,j;
  j = i = strlen(dc->hadata);
  if(name) j += strlen(name);
  if(data) j += strlen(data);
  j += 2; // = \n
  if(j >= 2048) {
    return;
  }
  sprintf(dc->hadata + i,"%s=%s\n",name,data);
}

static char *readinfile(char *fname)
{
  struct stat myst;
  if(stat(fname,&myst)) return NULL;
  if(myst.st_size <= 0) return NULL;
  FILE *fstk;
  if((fstk=fopen(fname,"r")) == NULL) return NULL;
  uint8_t *p=mymalloc(myst.st_size + 8);
  size_t n=fread(p,1,myst.st_size,fstk);
  fclose(fstk);
  p[n] = '\0';
  return (char *)p;
}
static void readparsemsg(devidcb *dc)
{
  if(dc->nmsg) { // free last sent one
    myfree(dc->nmsg);
    dc->nmsg = NULL;
  }
  if(dc->nmsg0 == NULL) { // see if there is another out/msg
    sprintf(dc->msgfile,"%s/%s.out",MYDATADIR,dc->id); // 1st
    if((dc->nmsg0=readinfile(dc->msgfile)) == NULL) {
      sprintf(dc->msgfile,"%s/%s.msg",MYDATADIR,dc->id); // 2nd
      if((dc->nmsg0=readinfile(dc->msgfile)) == NULL) {	
      }
    }
    if(dc->nmsg0 == NULL) {
      dc->msgfile[0] = '\0';
      return;
    }
    dc->nmsg1 = dc->nmsg0; // will be NULL if no file found    
  }
  // fill in dc->nmsg
  char *r,*q,*lsp,*lsr,llbuf[128];
  r = dc->nmsg1;
  q = llbuf;
  q += sprintf(q,"setmsg2 '");
  lsp = NULL;
  while(*r && (int)(q-llbuf) <= 64) {
    if(*r == '\n') *r = ' ';
    if(*r == ' ') { lsp = q; lsr = r; }
    *q++ = *r++;
  }
  if(*r) {
    if(lsp) {
      q = lsp;
      dc->nmsg1 = lsr + 1;
    } else {
      dc->nmsg1 = r;
    }
    *q++ = '\'';
    *q++ = '\0';
    dc->nmsg = mystrdup(llbuf);
  } else { // last piece. we are done
    *q++ = '\'';
    *q++ = '\0';
    dc->nmsg = mystrdup(llbuf);
    myfree(dc->nmsg0);
    dc->nmsg0 = NULL;
    // remove commands from .out files. leave generic .msg
    if(strstr(dc->msgfile,".msg") == NULL) unlink(dc->msgfile);
    dc->msgfile[0] = '\0';
  }
}

#define SENSORDATA_TEMPERATURE 1
#define SENSORDATA_HUMIDITY 2
#define SENSORDATA_VBAT 3
#define SENSORDATA_NOP 4
#define SENSORDATA_TIMESTAMP 5
#define SENSORDATA_CURSSID 6
#define SENSORDATA_FLAGSM1 7
#define SENSORDATA_UPTIME 8
#define SENSORDATA_VERSION 9
#define SENSORDATA_POWER 10
#define SENSORDATA_CURRENT 11
#define SENSORDATA_VOLT 12
#define SENSORDATA_DEVNAME 13

static int conf_sock=-1;
static struct sockaddr_in conf_addr;
static int conffiot(void)
{
  int n;
  devidcb *dc;
  uint8_t *p,buf[DATA_BUFSIZE];

  char lbuf[MAXPATHLEN];
  char sid[64],f_status[1024];
  //char f_ssids[1024],f_sensors[1024];
  
  uint32_t id;
  FILE *fp;
  uint fromlen;
  struct sockaddr_in from;

  if((conf_sock=socket(AF_INET,SOCK_DGRAM,0)) < 0) {
    mylog("Sender socket failed %s\n",strerror(errno));
    return -1;
  }
  if((p=(uint8_t *)lookup_v4host(S4ICONFSERVER)) == NULL) {
    mylog("Failed to lookup %s\n",S4ICONFSERVER);
    return -1;
  }
  memset(&conf_addr,0,sizeof(struct sockaddr_in));
  conf_addr.sin_family = AF_INET;
  conf_addr.sin_addr.s_addr = inet_addr((char *)p);
  conf_addr.sin_port = htons(S4ICONFSERVERPORT);
  mylog("Sender sock fd=%d\n",conf_sock);

  while(1) {
    fromlen = sizeof(from);
    n = recvfrom(conf_sock,buf,sizeof(buf),0,(struct sockaddr *)&from,&fromlen);

    if(n <= 0) { // error condition?
      if(n == 0) {
	mylog("error: Closing socket %d\n",conf_sock);
	close(conf_sock);
	goto endit;
      } else {
	mylog("error: read error %s\n",strerror(errno));
	if(errno == EAGAIN) mylog("More to read(fd=%d) EAGAIN\n",conf_sock);
      }
      continue;
    }

    //mylog("\n");
    if(debug>1) mylog("%d bytes from %s:%u\n",n,inet_ntoa(from.sin_addr),htons(from.sin_port));
    //rdump(buf,n); // |id(4)|data(n)|  data(n)=aes(|time(3)|txseq(1)|data|,k1) n=N*16
    //mylog("\n");

    id = PTR232BIT(buf);
    sprintf(sid,"%08X",id);

    if(n < 4 + 4 + 8) { // |id(4)| + ascon:|nonce(4)|ciphertext(0+)|tag(8)|
      mylog("error: UDP packet too short (%d bytes) id %s. dropped\n",n,sid);
      rdump(buf,n);
      continue;
    }
    
    if((dc=devid_find(sid,0)) == NULL) {
      mylog("error: Bad id %s\n",sid);
      continue;
    }
    
    mymutex(&mem_mutex,1);
    if(dc->inuse) {
      mymutex(&mem_mutex,0);
      mylog("error: %s In use by other thread. Skip\n",dc->id);
      continue;
    }
    dc->inuse = 3;
    mymutex(&mem_mutex,0);
    
    p = &buf[4];
    n -= 4;

    if(dc->protoversion == ASCON_TYPE_DATA) {
      if((n=ascondecryptinplace(p,(uint64_t)n,dc->v3_k1)) <= 0) {
        mylog("error: %s Bad decrypt\n",dc->id);
        dc->inuse = 0;
        continue;
      }
    } else {
      mylog("error: %s Cant decrypt:unsupported proto version=%d\n",dc->id,dc->protoversion);
      dc->inuse = 0;
      continue;
    }
    if(n < 6) { // was n <= 0
      mylog("error: %s Cant decrypt. n=%d\n",dc->id,n);
      dc->inuse = 0;
      continue;
    }

    time_t ltmp;
    time(&ltmp);
    uint32_t myts,mydf;
    if(dc->protoversion == ASCON_TYPE_DATA) {
      
      { // full-width diff of two Unix timestamps; masking to 16 bits wrapped every ~18.2h
        int64_t _d = (int64_t)ltmp - (int64_t)(*(uint32_t *)p);
        mydf = (uint32_t)(_d < 0 ? -_d : _d);
      }
      if(mydf > 600) {
	if(debug>0) mylog("error: %s time too far off. drop\n",dc->id);
	dc->inuse = 0;
	continue;
      }
      myts = (uint32_t)(ltmp - mydf);
      
      if(*(uint32_t *)p == dc->lastseen_uuid) {
	if(debug>0) mylog("%s stale. already seen (0x%08x) drop\n",dc->id,dc->lastseen_uuid);
	dc->inuse = 0;
	continue;
      }
      dc->lastseen_uuid = *(uint32_t *)p;
      
      dc->rxack = p[5]; // p[3];
      
      uint32_t lst=((*(uint32_t *)p)&0x0000FFFF);
      if((int)(lst - dc->lastseen_time) == 0) {
	if(debug>0) mylog("%s stale data. drop\n",dc->id);
	dc->inuse = 0;
	continue;
      }
      dc->lastseen_time = lst;
      
      if(dc->ack == p[4]) { // recv seq=p[2], we have already ack'd dc->ack.
	if(debug>0) mylog("%s seen packet %d. drop\n",dc->id,dc->ack);
	dc->inuse = 0;
	continue;
      }
      dc->ack = p[4];  // p[2] new data recieved!
            
      //if(debug>0) mylog("\n");
      
      if(mydf < SMS4IOT_FRESHENESS) {
        if(debug>1) mylog("%s Got fresh sensor data. nmsg=|%s|\n",dc->id,dc->nmsg?dc->nmsg:"NULL");
        dc->n = 0;
	/* 
	 * fresh sensor data indicates last exchange was good, so:
	 *  if no .out file, dc->v3_msg = dc->v3_dmsg (default) else
	 *  dc->v3_msg = contents of .out file and remove file.
	 *     OR
	 *  leave .out file in place until the response we get 
	 *  indicates change request sucessful. Then remove.
	 */
	readparsemsg(dc);
        if(dc->nmsg) dc->v3_msg = dc->nmsg; else dc->v3_msg = dc->v3_dmsg;
      }

      p += 6;
      n -= 6;

    } else {
      if(debug>0) mylog("error: %s unsupported proto\n",dc->id);
      dc->inuse = 0;
      continue;      
    }
    
    f_status[0] = '\0';
    //f_ssids[0] = '\0';
    //f_sensors[0] = '\0';

    while(n > 2) { // |ietype(2)|data|
      
      switch(*(uint16_t *)p) {
      case IE_STATUS: // data=|internet connected yes/no(4)|ip(4)|mask(4)|gw(4)|dns(4)|ssid(32)|0(1)|
	if(debug>0) mylog("%s IE_STATUS\n",dc->id);
	if(n < 20 + 32 + 1 + 2) { mylog("error: %s truncated IE_STATUS\n",dc->id); goto badpacket; }
	{
	  uint32_t stt=PTR232BITHOST(p+2);	  
	  if(debug>0) mylog("Local IP:%u.%u.%u.%u WiFi:%s Relay:%s\n",
		 p[6],p[7],p[8],p[9],p[22]?(char *)&p[22]:"",(stt&DEV_RELAY_ON)?"ON":"OFF");
	  
	}
	p += 20 + 32 + 1 + 2;
	n -= 20 + 32 + 1 + 2;
	break;

      case IE_STATUS2:
	if(debug>0) mylog("%s IE_STATUS2\n",dc->id);
	if(n < 8 + 4 + 8 + 8 + 8 + 4 + 4 + 32 + 2) { mylog("error: %s truncated IE_STATUS2\n",dc->id); goto badpacket; }
	// |ver(8)|statflg(4)|bssid(6)|rssi(2)|bssid(6)|rssi(2)|bssid(6)|rssi(2)|uptime(4)|vbat(4)|curssid(32)|
	{
	  uint32_t stt=PTR232BITHOST(p+2+8); // statflg(4)
	  char *qq,llbuf[34];
	  uint8_t *mr,i;
	  qq = f_status;
	  memcpy(llbuf,&p[2+8+4+8+8+8+4+4],32); // curssid(32)
	  llbuf[32] = '\0';
	  qq += sprintf(qq,"%.8s %s %s ssid:%s ", // %.8s limits ver to 8 chars
			p+2, // ver(8)
			(stt&DEV_RELAY_ON)?"ON":"OFF",
			(stt&DEV_INPIN_HIGH)?"H":"L",
			llbuf);
	  stt = *(uint32_t *)&p[2+8+4+8+8+8]; // uptime(4)
	  if(stt > 86400) sprintf(llbuf,"%d:days %d:hrs",(stt/86400),(stt%86400)/3600);
	  else sprintf(llbuf,"%d:hrs %d:mins",stt/3600,(stt%3600)/60);
	  qq += sprintf(qq,"uptime:%s\n",llbuf);

	  if((PTR232BITHOST(p+2+8)&DEV_BSSID_HISTORY)) {
	    mr = &p[2+8+4];
	    qq += sprintf(qq,"%u %02x%02x",*(uint32_t *)mr,mr[4],mr[5]);
	    mr += 4 + 2;
	    for(i=0;i<3;i++) {
	      qq += sprintf(qq," %02x:%02x:%02x:%02x:%02x:%02x",
			    mr[0],mr[1],mr[2],mr[3],mr[4],mr[5]);
	      mr += 6;
	    }
	    qq += sprintf(qq,"\n");
	  } else {
	    mr = &p[2+8+4];
	    for(i=0;i<3;i++) {
	      qq += sprintf(qq,"%02x:%02x:%02x:%02x:%02x:%02x %d\n",
			    mr[0],mr[1],mr[2],mr[3],mr[4],mr[5],*(int16_t *)&mr[6]);
	      mr += 8;
	    }
	  }
	  if(debug>0) mylog("%s\n",f_status);
	}
	p += 8+ 4 + 8 + 8 + 8 + 4 + 4 + 32 + 2;
	n -= 8+ 4 + 8 + 8 + 8 + 4 + 4 + 32 + 2;
	break;

      case IE_SENSORDATA: // |seqcnt|type|len|data|...|type|len|data|
	if(debug>0) mylog("%s:rx IE_SENSORDATA %db left. state:%d\n",dc->id,n,dc->state);
	/*
	 * output file for HA:
	 temperature=22.5
	 humidity=58
	 voltage=3.27
	 uptime=86400
	 relay=on
	 ssid=HomeWifi
	 dn=3.xl.nl
	*/
	char llbuf[256];
	p += 2;
	n -= 2;

	if(n < 1) { mylog("error: %s truncated sensordata\n",dc->id); goto badpacket; }
	int sn = (int)*p++;
	n--;
	while(sn > 0) {
	  int type,len;
	  if(n < 1) { mylog("error: %s truncated sensor type\n",dc->id); goto badpacket; }
	  if((*p)&0x80) {
	    int i = (int)((*p)&0x7F);
	    p++;
	    n--;
	    if(i == 1) {
	      if(n < 1) { mylog("%s error: truncated sensor type\n",dc->id); goto badpacket; }
	      type = (int)*p;
	      p++;
	      n--;
	    } else if(i == 2) {
	      if(n < 2) { mylog("%s error: truncated sensor type\n",dc->id); goto badpacket; }
	      type = (int)(((*p)<<8)|(*(p+1)));
	      p += 2;
	      n -= 2;
	    } else {
	      mylog("%s error bad type\n",__func__);
	      goto badpacket;
	    }
	  } else {
	    type = (int)(*p);
	    p++;
	    n--;
	  }
	  if(n < 1) { mylog("%s error: truncated sensor len\n",dc->id); goto badpacket; }
	  if((*p)&0x80) {
	    int i = (int)((*p)&0x7F);
	    p++;
	    n--;
	    if(i == 1) {
	      if(n < 1) { mylog("%s error: truncated sensor len\n",dc->id); goto badpacket; }
	      len = (int)*p;
	      p++;
	      n--;
	    } else if(i == 2) {
	      if(n < 2) { mylog("%s error: truncated sensor len\n",dc->id); goto badpacket; }
	      len = (int)(((*p)<<8)|(*(p+1)));
	      p += 2;
	      n -= 2;
	    } else {
	      mylog("%s error: bad length\n",__func__);
	      goto badpacket;
	    }
	  } else {
	    len = (int)(*p);
	    p++;
	    n--;
	  }
	  if(len < 0 || len > n) {
	    mylog("%s error: sensor len %d exceeds %d bytes remaining\n",dc->id,len,n);
	    goto badpacket;
	  }
	  if(len >= (int)sizeof(llbuf)) {
	    mylog("%s error: sensor len %d too large\n",dc->id,len);
	    goto badpacket;
	  }
	  {
	    int need = 0;
	    switch(type) {
	    case SENSORDATA_TEMPERATURE: case SENSORDATA_HUMIDITY: need = 1; break;
	    case SENSORDATA_VBAT: need = 2; break;
	    case SENSORDATA_TIMESTAMP: case SENSORDATA_FLAGSM1: case SENSORDATA_UPTIME:
	    case SENSORDATA_POWER: case SENSORDATA_CURRENT: case SENSORDATA_VOLT: need = 4; break;
	    }
	    if(len < need) {
	      mylog("%s error: sensor type %d len %d too short\n",dc->id,type,len);
	      goto badpacket;
	    }
	  }	  
	  switch(type) {
	  case SENSORDATA_TEMPERATURE:
	    sprintf(llbuf,"%d",(int)(*p) - 50);
	    update_devid_entry(dc,"temperature",llbuf);
	    //mqtt_devdata(dc,"temperature",llbuf);
	    //mylog(" Temp=%sC\n",llbuf);
	    break;
	  case SENSORDATA_HUMIDITY:
	    sprintf(llbuf,"%d",(int)(*p));
	    update_devid_entry(dc,"humidity",llbuf);
	    //mqtt_devdata(dc,"humidity",llbuf);
	    //mylog(" Humidity=%s%%\n",llbuf);
	    break;
	  case SENSORDATA_VBAT:
	    //sprintf(llbuf,"%d",*(int16_t *)p);
	    sprintf(llbuf,"%f", 2.02*((float)*(int16_t *)p)/1000.0); // 100kx2 /2 and 12db atten
	    update_devid_entry(dc,"adc",llbuf);
	    //mqtt_devdata(dc,"adc",llbuf);
	    //mylog(" V=%smV\n",llbuf);
	    break;	    
	  case SENSORDATA_TIMESTAMP:
	    {
	      time_t ts=(time_t)(*(uint32_t *)p);
	      sprintf(llbuf,"%s",ctime(&ts));
	    }
	    //update_devid_entry(dc->id,"timestamp",llbuf);	    
	    //mqtt_devdata(dc,"tstamp",llbuf);
	    //mylog(" TS=%s",llbuf);
	    break;
	  case SENSORDATA_CURSSID:
	    memcpy(llbuf,p,len);
	    llbuf[len] = '\0';
	    update_devid_entry(dc,"wifi",llbuf);
	    //mqtt_devdata(dc,"ssid",llbuf);
	    //mylog(" SSID=%s\n",llbuf);
	    break;
	  case SENSORDATA_FLAGSM1:
	    {
	      uint32_t stt=*(uint32_t *)p;
	      update_devid_entry(dc,"relay",(stt&DEV_RELAY_ON)?"on":"off");
	      //mqtt_devdata(dc,"state",(stt&DEV_RELAY_ON)?"ON":"OFF");
	      update_devid_entry(dc,"button",(stt&DEV_BUTTON_PUSHED)?"PUSHED":"RELEASED");
	      update_devid_entry(dc,"inpin",(stt&DEV_INPIN_HIGH)?"H":"L");
	      sprintf(llbuf,"%u",(stt>>24));
	      update_devid_entry(dc,"resets",llbuf);
	    }
	    break;	    
	  case SENSORDATA_UPTIME:
	    //sprintf(llbuf,"%u",*(uint32_t *)p);
	    tconvert(llbuf,*(uint32_t *)p,1); // hr min or day hr
	    //sprintf(llbuf,"%.5f",(double)(*(uint32_t *)p)/86400.0); // duration in fractions of a day
	    //mqtt_devdata(dc,"uptime",llbuf);
	    update_devid_entry(dc,"uptime",llbuf);
	    break;
	    
	  case SENSORDATA_VERSION:
	    memcpy(llbuf,p,len);
            llbuf[len] = '\0';
	    update_devid_entry(dc,"version",llbuf);
	    // save locally
	    if(dc->swver) {
	      if(strcmp(dc->swver,llbuf)) { // update device config data
		myfree(dc->swver);
		dc->swver = mystrdup(llbuf);
		
	      }
	    } else {
	      dc->swver = mystrdup(llbuf);
	      
	    }
	    break;

          case SENSORDATA_DEVNAME:
            memcpy(llbuf,p,len);
            llbuf[len] = '\0';
            //mqtt_devdata(dc,"devname",llbuf);
	    update_devid_entry(dc,"devname",llbuf);
            //mylog(" DEVNAME=%s\n",llbuf);
            break;
	    
         case SENSORDATA_POWER:
            sprintf(llbuf,"%.2f",*(float *)p);
	    //mqtt_devdata(dc,"power",llbuf);
	    update_devid_entry(dc,"power",llbuf);
            //mylog(" AC=%sW\n",llbuf);
            break;
          case SENSORDATA_CURRENT:
            sprintf(llbuf,"%.2f",*(float *)p);
	    //mqtt_devdata(dc,"current",llbuf);
	    update_devid_entry(dc,"current",llbuf);
            //mylog(" AC=%sA\n",llbuf);
            break;
          case SENSORDATA_VOLT:
            sprintf(llbuf,"%.2f",*(float *)p);
	    //mqtt_devdata(dc,"voltage",llbuf);
	    update_devid_entry(dc,"voltage",llbuf);
            //mylog(" AC=%sV\n",llbuf);
            break;
	    
	    
	  default:
	    mylog("error: Unk type=0x%x len=%d\n",type,len);
	    rdump(p,len);
	    break;
	  }	  
	  p += len;
	  n -= len;
	  sn--;
	}
	//sprintf(llbuf,"%u",(uint32_t)time(NULL));
	//tconvert(llbuf,(uint32_t)time(NULL),0);
	pytconvert(llbuf,(uint32_t)time(NULL));
	//mqtt_devdata(dc,"lastseen",llbuf);
	update_devid_entry(dc,"lastseen",llbuf);

	if(dc->hadata) {
	  if(debug>0) mylog("%s:HA=%s\n",dc->id,dc->hadata);
	  sprintf(lbuf,"%s/%s.tmp",MYDATADIR,dc->id);
	  if((fp=fopen(lbuf,"w"))) {
	    fprintf(fp,"%s",dc->hadata);
	    fclose(fp);
	    char *_tf=(char *)mymalloc(strlen(lbuf)+16);
	    sprintf(_tf,"%s/%s",MYDATADIR,dc->id);
	    rename(lbuf,_tf); // writes over old
	    myfree(_tf);
	  }
	  myfree(dc->hadata);
	  dc->hadata = NULL;
	}
	break;
		
      case IE_NULL: // terminate
	if(debug>0) mylog("%s IE_NULL\n",dc->id);
	p += 2;
	n = 0; // n -= 2;
	break;

      default:
	mylog("error: %s Unknown IE=%u\n",dc->id,*(uint16_t *)p);
	n = 0;
	break;
      }
    }
 badpacket:
    dc->inuse = 0;
  }
 endit:
  mylog("Ending...\n");
  //conffiotrunning = 0;
  return -1;
}

static void devid_free(devidcb *dc)
{
  if(dc->id) myfree(dc->id);
  if(dc->swver) myfree(dc->swver);
  if(dc->hadata) myfree(dc->hadata);
  if(dc->v3_k2) myfree(dc->v3_k2);
  if(dc->v3_k1) myfree(dc->v3_k1);

  if(dc->v3_dmsg) myfree(dc->v3_dmsg);
  //if(dc->v3_msg) myfree(dc->v3_msg);
  if(dc->nmsg) myfree(dc->nmsg);
  if(dc->nmsg0) myfree(dc->nmsg0);
  if(dc->out) myfree(dc->out);
  myfree(dc);
}

/*
echo -n '10000110 5.X031.NL 4 CUHYPB6ZD0TU:7J9ZTRDRYQP40L34GG1VFWKB6GL/DAMJ*5NO$LY*PVUJ0IO5JR23C49WLQP05L:51E71UIMDHP3RQ3UL*0I'| sudo ./url2qr -

static const char BASE45_CHARSET[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
 */
static uint8_t _C2I[256] = { // orig: static char _C2I[256]
  255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
  255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
  36, 255,255,255,  37, 38,255,255, 255,255, 39, 40, 255, 41, 42, 43,
  0,   1,  2,  3,   4,  5,  6,  7,   8,  9, 44,255, 255,255,255,255, 
  
  255, 10, 11, 12,  13, 14, 15, 16,  17, 18, 19, 20,  21, 22, 23, 24, /* uppercase */
  25, 26, 27, 28,  29, 30, 31, 32,  33, 34, 35, 35, 255,255,255,255,
  255, 10, 11, 12,  13, 14, 15, 16,  17, 18, 19, 20,  21, 22, 23, 24, /* lowercase */
  //orig 25, 26, 27, 28,  29, 30, 31, 32,  33, 34, 35, 35, 255,255,255,255,
  25, 26, 27, 28,  29, 30, 31, 32,  33, 34, 35,255, 255,255,255,255,
  
  255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255, 
  255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
  255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
  255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
  
  255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
  255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
  255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
  255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
};
static int base45_decode(unsigned char * dst,size_t *_max_dst_len,const char *src,size_t src_len) {
  size_t out_len = 0, max_dst_len;
  max_dst_len = _max_dst_len  ? *_max_dst_len : src_len;  
  if(dst == NULL && _max_dst_len == NULL) return -2;
  if(src == NULL) return -2;
  if(src_len == 0) src_len = strlen(src);
  for(int i=0;i<src_len;i+=3) {
    int x,a,b;
    if(src_len - i < 2) return -1;
    //if((255 == (a = _C2I[(int)src[i]])) || (255 == (b = _C2I[(int)src[i+1]]))) return -1;
    if((255 == (a = _C2I[(uint8_t)src[i]])) || (255 == (b = _C2I[(uint8_t)src[i+1]]))) return -1;
    x = a + 45 * b;
    if(src_len - i >= 3) {
      //if(255 == (a = _C2I[(int)src[i+2]])) return -1;
      if(255 == (a = _C2I[(uint8_t)src[i+2]])) return -1;
      x += a * 45 * 45;
      if(out_len < max_dst_len && dst) dst[out_len] = x / 256;
      out_len++;
      x %= 256;
    }
    if(out_len < max_dst_len && dst) dst[out_len] = x;
    out_len++;
  }
  if(_max_dst_len) *_max_dst_len = out_len;
  return 0;
}
static void devid_refresh(void)
{
  devidcb *dc;
  FILE *fp;
  char buf[MAXPATHLEN];
  uint8_t k1[64],k2[64];

  // scan MYDEVDIR=sms4iot/devices dir for devid/k2/k1
  DIR *dirp;
  struct dirent *dp;
  if((dirp=opendir(MYDEVDIR)) == (DIR *)0) {
    mylog("Cannot open %s directory\n",MYDEVDIR);
    return;
  }
  while((dp=readdir(dirp))) {
    if(strcmp(dp->d_name,".") == 0 || strcmp(dp->d_name,"..") == 0) continue;
    // check if dp->d_name is in the form of a device id
    sprintf(buf,"%s/%s",MYDEVDIR,dp->d_name);
    if((fp=fopen(buf,"r")) == NULL) {
      mylog("error: Cannot open %s\n",buf);
      continue;
    }
    while(fgets(buf,sizeof(buf),fp)) { // id fqdn proto k2|k1
      if(buf[0] == '#') continue; // comment
      char *p,*q,*fqdn,*proto,*defmsg=NULL;
      uint8_t k2k1[128];

      if(buf[strlen(buf) - 1] == 0x0A) buf[strlen(buf) - 1] = '\0';
      
      // devid
      q = buf;
      for(p=q;*p && *p != ' ';p++) ;
      if(*p == '\0') {
	mylog("error: Not enough args in %s\n",dp->d_name);
        continue;
      }
      *p++ = '\0';
      if(strcmp(q,dp->d_name)) { // redundant but check
        mylog("error: dev id and name dont match %s != %s\n",q,dp->d_name);
        continue;
      }
      // fqdn
      q = p;
      for(p=q;*p && *p != ' ';p++) ;
      if(*p == '\0') {
        mylog("error: Not enough args in %s\n",dp->d_name);
        continue;
      }
      *p++ = '\0';
      fqdn = q;
      //mylog("%s fqdn=|%s|\n",dp->d_name,fqdn);
      // proto
      q = p;
      for(p=q;*p && *p != ' ';p++) ;
      if(*p == '\0') {
        mylog("error: Not enough args B in %s\n",dp->d_name);
        continue;
      }
      *p++ = '\0';
      proto = q;
      if(atoi(proto) != ASCON_TYPE_DATA) {
        mylog("error: Unsupported protocol %s in %s\n",proto,dp->d_name);
        continue;
      }
      // k2k1 64 bytes base45 encoded
      q = p; // since base45, may start with " " space and have many in btwn
      size_t no = sizeof(k2k1);
      if(base45_decode(k2k1,&no,q,strlen(q)) || no != 64) {
	mylog("error: Failed to decode k2k1 in %s (%d bytes)\n",dp->d_name,no);
	mylog("error: %d|%s|\n",strlen(q),q);
	rdump((uint8_t *)q,strlen(q));
        continue;
      }
      memcpy(k2,k2k1,32);
      memcpy(k1,k2k1+32,32);
      //rdump(k2k1,64);
      
      if((dc=devid_find(dp->d_name,1)) == NULL) {
	mylog("error: %s bad or in use.\n",dp->d_name);
	continue;
      }
      mymutex(&mem_mutex,1);
      if(dc->inuse) {
	mymutex(&mem_mutex,0);
	mylog("error: %s in use by other thread. Skip\n",dc->id);
	dc->dflg = 3; // we saw it. but deal with it on next refresh
	continue;
      }
      dc->inuse = 6;
      mymutex(&mem_mutex,0);
      
      dc->protoversion = atoi(proto);
      dc->dflg = 3; // mark temporaily as good here for drop below
      if(dc->v3_k2) { myfree(dc->v3_k2); dc->v3_k2 = NULL; }      
      dc->v3_k2 = mymemdup(k2,32);
      if(dc->v3_k1) { myfree(dc->v3_k1); dc->v3_k1 = NULL; }      
      dc->v3_k1 = mymemdup(k1,32);
      if(dc->v3_dmsg) {
	if(dc->v3_msg == dc->v3_dmsg) dc->v3_msg = NULL;
	myfree(dc->v3_dmsg); dc->v3_dmsg = NULL;
      }
      if(defmsg) dc->v3_dmsg = mystrdup(defmsg);
      else {
	char lbuf[64];
	sprintf(lbuf,"setmsg2 'hello there %s'",dc->id);
	dc->v3_dmsg = mystrdup(lbuf);
      }
      dc->inuse = 0;
    }
    fclose(fp);
  }
  closedir(dirp);

  // drop ones we did not find in conf file above 
  mymutex(&devid_mutex,1);
  devid_mutex_inuse = 1;
  devidcb *dcn,*dc1;
  dc = dc0; // // current head
  dc1 = NULL; // new head
  while(dc) {
    dcn = dc->next;
    if(dc->dflg != 3 && dc->inuse == 0) { // may have to skip if inuse
      devid_free(dc);
      dc = dcn;
      continue;
    }
    dc->dflg = 0; // reset to normal
    
    dc->next = dc1; // rebuild new head
    dc1 = dc;
    
    dc = dcn;
  }
  dc0 = dc1;
  mymutex(&devid_mutex,0);
  devid_mutex_inuse = 0;
  if(debug>1) mylog("Loading device files done\n");
}

static int devid_comm(devidcb *dc)
{
  int i,nd,nk1time,nk2time,n,nargs;
  uint8_t out[DATA_BUFSIZE];
  char *a0,*args[NARGS];

  if(dc->protoversion == ASCON_TYPE_DATA) {

    mylog("%s rack=%d txseq=%d\n",dc->id,dc->rxack,dc->txseq);
    if(dc->rxack == dc->txseq) {
      
      uint32_t tt=(uint32_t)time(NULL);
      dc->rtt = (int)(tt - dc->lasttx);
      dc->lasttx = tt;
      
      dc->txseq++;
    }
  } else {
    return 0; // unsuported proto
  }
  
  a0 = NULL;

  time_t ltmp;
  time(&ltmp);
  if((int)(ltmp - dc->ttime) < S4ICONFSERVER_SPACING) {
    mylog("error: %s Too frequent tx\n",dc->id);
    return 0;
  }
  if(dc->n) {
    if(debug>1) mylog("%s resend seq=%d\n",dc->id,dc->txseq);
    goto resend;
  }
  
  // setmsg "a message"  OR wifiset login pass
  if(dc->v3_msg == NULL) dc->v3_msg = dc->v3_dmsg; // just in case  
  a0 = mystrdup(dc->v3_msg); // working copy
  { char *p; for(p=a0;*p;p++) if(*p == '\'') *p = '"'; }
  if(debug>0) mylog("%s ===|%s|===\n",dc->id,a0);  
  if((nargs=lparse(a0,args,NARGS,' ')) < 1) {
    mylog("%s error: Not enough args %d\n",dc->id,nargs);
    goto endit;
  }
    
  memset(out,0,sizeof(out));
  nd = 0;
    
  *(uint32_t *)out = dc->protoversion;
  
  nd += 4;
  if(ahex2bin(&out[nd],sizeof(out)-nd,dc->id) != 4) {
    mylog("%s error: bad id\n",dc->id);
    goto endit;
  }
    
  nd += 4;

  nk2time = nd; // time nonce used for k2 scramble
  nd += 4;
  nk1time = nd; // timestamp+txseq used with k1 data()
  nd += 6;
  
  if(strcmp(args[0],"wait") == 0) { // do nothing but query for any data R
    if(debug>0) mylog("%s %s\n",dc->id,args[0]);
    n = 0;
    goto queryonly;
  } else if(strcmp(args[0],"status") == 0) {
    if(debug>0) mylog("%s %s\n",dc->id,args[0]);
    SET16BIT(&out[nd],IE_STATUS); // req=||
    n = 2;
  } else if(strcmp(args[0],"sensorstat") == 0) {
    if(debug>0) mylog("%s %s\n",dc->id,args[0]);
    SET16BIT(&out[nd],IE_SENSORSTAT); 
    n = 2;
  } else if(strcmp(args[0],"wifiset") == 0 && nargs > 2) { // .. ssid pass
    if(debug>0) mylog("%s %s (%s,%s)\n",dc->id,args[0],args[1],args[2]);
    SET16BIT(&out[nd],IE_WIFISET); // req=|ssid(32)|0(1)|pass(32)|0(1)|
    memcpy(&out[nd+2],args[1],min(32,strlen(args[1])));
    memcpy(&out[nd+2+32+1],args[2],min(32,strlen(args[2])));
    n = 2 + 32 + 1 + 32 + 1;
  } else if(strcmp(args[0],"loginset") == 0 && nargs > 1) { // .. login
    if(debug>0) mylog("%s %s (%s)\n",dc->id,args[0],args[1]);
    SET16BIT(&out[nd],IE_LOGINSET); // req=|pass(32)|0(1)|    
    memcpy(&out[nd+2],args[1],min(32,strlen(args[1])));
    n = 2 + 32 + 1;
  } else if(strcmp(args[0],"setmsg") == 0 && nargs > 1) { // .. msg
    if(debug>0) mylog("%s %s (%s)\n",dc->id,args[0],args[1]);
    SET16BIT(&out[nd],IE_SETMSG); // req=|msg(32)|0(1)|
    memcpy(&out[nd+2],args[1],min(32,strlen(args[1])));
    n = 2 + 32 + 1;
  } else if(strcmp(args[0],"setmsg2") == 0 && nargs > 1) { // .. msg
    if(debug>0) mylog("%s %s (%s)\n",dc->id,args[0],args[1]);
    SET16BIT(&out[nd],IE_SETMSG2); // req=|msg(64)|0(1)|
    memcpy(&out[nd+2],args[1],min(64,strlen(args[1])));
    n = 2 + 64 + 1;    
  } else if(strcmp(args[0],"wifiscan") == 0) {
    if(debug>0) mylog("%s %s\n",dc->id,args[0]);
    //SET16BIT(&out[nd],IE_WIFISCAN); // req=||
    SET16BIT(&out[nd],IE_WIFISCANSSID); // req=||    
    n = 2;
  } else if(strncmp(args[0],"relay",5) == 0) {
    if(debug>0) mylog("%s %s\n",dc->id,args[0]);
    SET16BIT(&out[nd],IE_RELAY); // req=|onoff(1)|
    out[nd+2] = (args[0])[5];
    n = 1 + 2;
  } else {
    dc->n = 0; // clear retransmit  
    if(debug>0) mylog("%s error: Unknown cmd(%d args): |%s|%s|%s|\n",dc->id,nargs,
		      args[0],args[1]?args[1]:"-",args[2]?args[2]:"-");
    goto endit;
  }

  *(uint32_t *)&out[nk1time] = (uint32_t)time(NULL);
  if(dc->protoversion == ASCON_TYPE_DATA) {
    out[nk1time+4] = dc->txseq;
    out[nk1time+5] = dc->ack;
    n += 6;
  } else {
    mylog("%s error: could not encode: unsupported prot version=%d\n",dc->id,dc->protoversion);
    goto endit;
  }
  
  SET16BIT(&out[nk1time+n],IE_NULL); // Terminate
  n += 2;

  if(debug>0) mylog("%s new send seq=%u\n",dc->id,dc->txseq);
  //rdump(&out[nk1time],n);

  //mylog("+++ %s:curkey=\n",__func__); rdump(curkey,32);

  if(dc->protoversion == ASCON_TYPE_DATA) {
    n = asconencryptinplace(&out[nk1time],n,dc->v3_k1);
  } else {
    mylog("%s error: could not encrypt: unsupported prot version=%d\n",dc->id,dc->protoversion);
    goto endit;
  }
  if(n <= 0) {
    mylog("%s error: could not encrypt\n",dc->id);
    goto endit;
  }

  //rdump(&out[nk1time],n);
  //mylog("send BB\n");
  
 queryonly:  
  dc->sendcnt = 0;
  dc->n = n;
  dc->nk2time = nk2time;
  dc->nk1time = nk1time;
  i = 4 + 4 + 4 + n + 12;
  //mylog("send BB i=%d\n",i);
  if(dc->out) myfree(dc->out);
  //dc->out = mymalloc(i+64); // add workspace
  //memcpy(dc->out,out,i);
  dc->out = mymemdup(out,i);

  //mylog("send CC\n");

 resend:
  n = dc->n;
  nk1time = dc->nk1time;
  nk2time = dc->nk2time;
  i = 4 + 4 + 4 + n + 12;
  //memset(out,0,sizeof(out));
  memcpy(out,dc->out,i); // sizeof out > dc->out since we use high out for hash workspace.

  if(dc->sendcnt++ >= 9) {
    // clear retransmit
    dc->n = 0;
    mylog("%s error: could not communicate (proto:%d)\n",dc->id,dc->protoversion);
    goto endit;
  }

  *(uint32_t *)&out[nk2time] = (uint32_t)time(NULL);
  memcpy(&out[nk1time+n],dc->v3_k2,32); // append hash to data sizeof out > dc->out
  if(dc->protoversion == ASCON_TYPE_DATA) {
    asconhash(&out[nk1time+n],&out[nk2time],n+4+32);
  } else {
    mylog("%s error: do not support proto:%d)\n",dc->id,dc->protoversion);
    goto endit;
  }
  //mylog("sendto len=%d\n",i);
  mylog("%s tx:proto=%d seq=%u ack=%u n=%d rtt=%ds\n",dc->id,dc->protoversion,dc->txseq,dc->ack,i,dc->rtt);
  //rdump(out,i);
  sendto(conf_sock,out,i,0,(struct sockaddr *)&conf_addr,sizeof(conf_addr));
  dc->ttime = (uint32_t)time(NULL);
  //sleep(3);
  // timeout?
  
 endit:
  if(a0) myfree(a0);
  return 0;
}

static void readconfigfile(void)
{
  FILE *fp;
  char buf[1024];

  LOGFILE_MAXSIZE = LOGFILE_MAXSIZE_DEFAULT;
  LOGFILE_MAXNUMBER = LOGFILE_MAXNUMBER_DEFAULT;
  strcpy(S4ICONFSERVER,S4ICONFSERVER_DEFAULT);
  S4ICONFSERVERPORT = S4ICONFSERVERPORT_DEFAULT;
  sprintf(MYDEVDIR,"%s/devices",CURDIR);
  sprintf(MYDATADIR,"%s/data",CURDIR);
  S4ICONFSERVER_SPACING = S4ICONFSERVER_SPACING_DEFAULT;
  if((fp=fopen(MYCONFFILE,"r"))) {
    int n;
    char *args[NARGS];
    while(fgets(buf,sizeof(buf),fp)) {
      if(buf[0] == '#' || buf[0] == ';') continue;
      n = lparse(buf,args,NARGS,':');
      if(n < 2) continue;
      if(strcmp(args[0],"S4ICONFSERVER") == 0) {
        strncpy(S4ICONFSERVER,args[1],sizeof(S4ICONFSERVER)-1);
	S4ICONFSERVER[sizeof(S4ICONFSERVER)-1] = '\0';
      } else if(strcmp(args[0],"S4ICONFSERVERPORT") == 0) {
        S4ICONFSERVERPORT = atoi(args[1]);
      } else if(strcmp(args[0],"S4ICONFSERVER_SPACING") == 0) {
	S4ICONFSERVER_SPACING = atoi(args[1]);
      } else if(strcmp(args[0],"debug") == 0) {
        debug = atoi(args[1]);
      } else if(strcmp(args[0],"LOGFILE_MAXSIZE") == 0) {
        LOGFILE_MAXSIZE = atoi(args[1]);
      } else if(strcmp(args[0],"LOGFILE_MAXNUMBER") == 0) {
        LOGFILE_MAXNUMBER = atoi(args[1]);
      } else if(strcmp(args[0],"MYDEVDIR") == 0) {
        strncpy(MYDEVDIR,args[1],sizeof(MYDEVDIR)-1);
	MYDEVDIR[sizeof(MYDEVDIR)-1] = '\0';
      } else if(strcmp(args[0],"MYDATADIR") == 0) {
        strncpy(MYDATADIR,args[1],sizeof(MYDATADIR)-1);
	MYDATADIR[sizeof(MYDATADIR)-1] = '\0';
      } else {
        mylog("Unknown conf item |%s|\n",args[0]);
      }
    }
    fclose(fp);
  } else if((fp=fopen(MYCONFFILE,"w"))) {
    fprintf(fp,"S4ICONFSERVER:%s\n",S4ICONFSERVER);
    fprintf(fp,"S4ICONFSERVERPORT:%d\n",S4ICONFSERVERPORT);
    fprintf(fp,"S4ICONFSERVER_SPACING:%d\n",S4ICONFSERVER_SPACING);
    fprintf(fp,"debug:%d\n",debug);
    fprintf(fp,"LOGFILE_MAXSIZE:%d\n",LOGFILE_MAXSIZE);
    fprintf(fp,"LOGFILE_MAXNUMBER:%d\n",LOGFILE_MAXNUMBER);
    fprintf(fp,"MYDEVDIR:%s\n",MYDEVDIR);
    fprintf(fp,"MYDATADIR:%s\n",MYDATADIR);
    fclose(fp);
  } else {
    mylog("warning: could not read or create config file |%s|\n",MYCONFFILE);
  }
}
int main(int argc,char *argv[])
{
  pthread_t ths[10];

  if(argc > 1) {
    printf("%s\n",VERSION);
    return 0;
  }
  char path[MAXPATHLEN],resolved_path[MAXPATHLEN],*p;
  strcpy(path,argv[0]);
  if(realpath(path,resolved_path) == NULL) {
    printf("error |%s|%s|\n",path,resolved_path);
    return -1;
  }
  
  sprintf(LOGFILE,"%s.log",resolved_path);
  sprintf(MYCONFFILE,"%s.conf",resolved_path);
  strcpy(CURDIR,resolved_path);
  if((p=strrchr(CURDIR,'/'))) *p = '\0';

  flog = fopen(LOGFILE,"a"); // start logging
  readconfigfile();

  mylog("S4ICONFSERVER:%s\n",S4ICONFSERVER);
  mylog("S4ICONFSERVERPORT:%d\n",S4ICONFSERVERPORT);
  mylog("debug:%d\n",debug);
  mylog("LOGFILE_MAXSIZE:%d\n",LOGFILE_MAXSIZE);
  mylog("LOGFILE_MAXNUMBER:%d\n",LOGFILE_MAXNUMBER);
  
  setsid();
  blocksignals(1);

  if(pthread_create(&ths[0],NULL,(void *)conffiot,NULL) != 0) {
    mylog("error: Could not create config thread: %s\n",strerror(errno));
    return -1;
  }
  mylog("%s\n",VERSION);

  // periodic poll
  devidcb *dc;
  uint8_t tck=0;
  while(1) {
    if(startshutdown) {
      mymutex(&devid_mutex,1); // wait+block all db operations before exit
      devid_mutex_inuse = 1;
      mylog("Shutdown or inactive so exit\n");
      break;
    }
    // scan infile for devid,k2,k1,default_message. send to gw. mqttlistener() may inject message.
    // responses from gw handled by conffiot().
    if((tck++ % 3) == 0) {
      devid_refresh(); // update devid info from file as needed
      if(debug>0) mymemstat();
      logfile_rollover();
    }
    for(dc=dc0;dc;dc=dc->next) {
      
      mymutex(&mem_mutex,1);
      if(dc->inuse) {
	mymutex(&mem_mutex,0);
	mylog("%s In use by other thread (%d). Skip\n",dc->id,dc->inuse);
	continue;
      }
      dc->inuse = 1;
      mymutex(&mem_mutex,0);
      
      devid_comm(dc); // send or resend to s4i device via gw until acked or timed out

      dc->inuse = 0;
    }
    sleep(S4ICONFSERVER_SPACING); // minimum 6sec spacing between requests
  }

  return 0;
}


#ifdef MYDNSSTATIC
static uint32_t my_rnd(void) /* replace with yours */
{
  uint64_t tmp;
  int fd;
  if((fd=open("/dev/urandom",O_RDONLY)) > 0) {
    read(fd,&tmp,sizeof(tmp));
    close(fd);
  } else {
    mylog("cant open /dev/urandom\n");
  }
  return (uint32_t)tmp;
}

/* my getaddrinfo() static replacement */
/*copyright*/
/*************************************************
 * Rick's ancient DNS handler copyright 1995-2026 RHLamb
 * The MIT License (MIT)
 * This library is written and maintained by Richard Lamb.
 * Copyright (c) 1995-2025 Richard Lamb lamb@xtcn.com
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *************************************************/
/* TYPES */
#define DNS_UNK 0
#define DNS_A 1
#define DNS_NS 2
#define DNS_CNAME 5
#define DNS_SOA 6
#define DNS_NULL 10
#define DNS_PTR 12
#define DNS_HINFO 13
#define DNS_MX 15
#define DNS_TXT 16
#define DNS_AAAA 28
/* EXT QTYPES */
#define DNS_AXFR 252
/* CLASS */
#define DNS_IN 1
#define DNS_CH 3
/* rfc1035 pstart = start of dns message, ie, at ID field */
static uint8_t *dns_copy_name(char *q,uint8_t *p,uint8_t *pstart,int len)
{
  int i,j;
  uint8_t *r;
  static int depth=0;
  if(depth >= 32) return (uint8_t *)0; // max 127 dots. pick 32
  depth++;  
  while(*p) {
    if( (*p & 0xC0) == 0xC0 ) {
      j = PTR216BIT(p)&0x3FFF;
      if(j > len) {
        mylog("error: corrupted dns C0 length %d > %d\n",j,len);
        rdump(pstart,len);
        return (uint8_t *)0;
      }
      r = p + 2;
      p = pstart + j;
      if(dns_copy_name(q,p,pstart,len) == (uint8_t *)0) return (uint8_t *)0;
      depth--;
      return r;
    }
    j = *p++;
    if(j >= 64) {
      mylog("error: label too long %d >= 64\n",j);
      return (uint8_t *)0;
    }
    for(i=0;i<j;i++) *q++ = *p++;
    if((int)(p - pstart) > len) {
      mylog("error: past end of packet 1\n");
      depth--;
      return (uint8_t *)0;
    }
    if(*p) *q++ = '.';
  }
  *q = '\0';
  p++; /* skip zero byte */
  depth--;
  if((int)(p - pstart) > len) {
    mylog("error: past end of packet 2\n");
    return (uint8_t *)0;
  }
  return p;
}
static uint8_t *dns_convert(char *s,uint8_t *p)
{
  char *q,*r;
  int k;  
  /* convert into len encoded */
  q = (char *) p++;
  r = s;
  k = 0;
  while(*r) {
    if(*r == '.') {
      *q = k;
      k = 0;
      q = (char *) p++; /* save the location */
      r++;
    } else {
      *p++ = *r++;
      k++;
    }
  }
  if(strlen(s) > 0 && *(r-1) != '.') { // add a "." if there wasnt one for a fqdn
    *q = k;
    k = 0;
    q = (char *) p++; /* save the location */
  }
  *q = k;
  return p;
}

static int dns_query(uint8_t *buf,char *dn,uint16_t type,uint16_t id)
{
  uint8_t *p;
  p = buf;
  // redo coersion so that odd boundaries work. crashes otherwise.  
  *(uint16_t *)&p[0] = htons(id); /* id */
  *(uint16_t *)&p[2] = htons(0x0130); /* flg 0x0100 or 0x0130 = CD bit !!! */
  *(uint16_t *)&p[4] = htons(1); /* qnct */
  *(uint16_t *)&p[6] = 0; /* acnt */
  *(uint16_t *)&p[8] = 0; /* nscnt */
  *(uint16_t *)&p[10] = 0;
  /* q's */
  p = dns_convert(dn,&p[12]);
  p[0] = (type>>8);
  p[1] = (type&0xFF);
  p += 2;  
  p[0] = 0;
  p[1] = 1;
  p += 2;
  return (int)(p - buf);
}


static int dnsdecode(uint8_t *pstart,int len,char *qname,uint16_t dnsid)
{
  int i,n=0;
  uint8_t *p = pstart;
  uint16_t id = PTR216BIT(p);
  if(id != dnsid) {
    mylog("error:ID mismatch %04X != %04X\n",id,dnsid);
    n = -2;
    goto drxdone;
  }
  p += 2;
  uint16_t flg = PTR216BIT(p);
  p += 2;
  uint16_t qdcount = PTR216BIT(p);
  p += 2;
  uint16_t ancount = PTR216BIT(p);
  p += 2;
  uint16_t nscount = PTR216BIT(p);
  p += 2;
  uint16_t arcount = PTR216BIT(p);
  p += 2;
  if((int)(p - pstart) > len) {
    mylog("error:buffer overflow 1\n"); 
    n = 0;
    goto drxdone;
  }
  if(qdcount == 0) {
    mylog("error:No queries\n"); 
    n = 0;
    goto drxdone;
  }
  if(ancount == 0) {
    mylog("error:No responses\n"); 
    n = 0;    
    goto drxdone;
  }
  p = dns_copy_name(qname,p,pstart,len);
  if(p == (uint8_t *)0) {
    mylog("error: qname\n");
    n = 0;
    goto drxdone;
  }
  uint16_t type = PTR216BIT(p);
  p += 2;
  uint16_t dclass = PTR216BIT(p);
  p += 2;
  if((int)(p - pstart) > len) {
    mylog("error:buffer overflow 2\n"); 
    n = 0;
    goto drxdone;
  }
  qdcount--;
  if(dclass != DNS_IN) {
    mylog("error:unsupported class\n"); 
    n = 0;
    goto drxdone;
  }
  int kk;
  uint16_t ltype;
  switch(type) {
  case DNS_A:
    while(ancount-- > 0) {
      p = dns_copy_name(qname,p,pstart,len);
      ltype = PTR216BIT(p);
      p += 2;
      p += 2; // class
      p += 4; // ttl
      kk = PTR216BIT(p); // len
      p += 2;
      n = kk;
      if((int)((p+kk) - pstart) > len) {
	mylog("error:buffer overflow A\n");
	n = 0;
	goto drxdone;
      }
      if(ltype == type) { // skip DNS_CNAMEs
	for(i=0;i<n;i++) *pstart++ = *p++;
	break; // take just 1st one
      }
      p += kk;
    }
    break;
  case DNS_AAAA: // may get this too
    while(ancount-- > 0) {
      p = dns_copy_name(qname,p,pstart,len);
      ltype = PTR216BIT(p);
      p += 2;
      p += 2; // class
      p += 4; // ttl
      kk = PTR216BIT(p); // len
      p += 2;
      n = kk;
      if((int)((p+kk) - pstart) > len) {
        mylog("error:buffer overflow AAAA\n");
        n = 0;
        goto drxdone;
      }      
      if(ltype == type) { // skip DNS_CNAMEs
	for(i=0;i<n;i++) *pstart++ = *p++;
	break; // take just 1st one
      }
      p += kk;
    }
    break;
  default:
    mylog("error:Unknown record type=%d\n",type); 
    n = 0;
    break;
  }
 drxdone:
  return n;
}
/*end copyright*/

#define DNSRESIP "8.8.8.8"
//#define DO_AAAA  // define if you want IPv6 addr
static int mydnslookup(uint8_t *out,int olen,char *str)
{
  int i,n,dnsk;
  struct sockaddr_in daddr;
  uint16_t dnsid;
  
  dnsk = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
  if(dnsk < 0) {
    mylog("error:Create UDP socket failed\n");
    return -1;
  }
  memset(&daddr,0,sizeof(daddr));
  daddr.sin_family = AF_INET;
  daddr.sin_port = htons(53);
  daddr.sin_addr.s_addr = inet_addr(DNSRESIP);
  
  uint8_t lbuf[512];
  dnsid = (uint16_t)my_rnd();
#ifdef DO_AAAA
  n = dns_query(lbuf,str,DNS_AAAA,dnsid);
#else // DO_AAAA  
  n = dns_query(lbuf,str,DNS_A,dnsid);
#endif // DO_AAAA  
  i = sendto(dnsk,lbuf,n,0,(struct sockaddr *)&daddr,sizeof(struct sockaddr_in));  
  
  fd_set read_fds;
  struct timeval timeout;
  FD_ZERO(&read_fds);
  FD_SET(dnsk,&read_fds);
  timeout.tv_sec = 1;
  timeout.tv_usec = 0;
  i = select(dnsk+1,&read_fds,NULL,NULL,&timeout);
  if(i > 0 && FD_ISSET(dnsk,&read_fds) == 0) {
    mylog("error:select returned %d but no read bytes\n",i);
    i = -1;
  }
  if(i <= 0) {
    mylog("error:timeout\n");
    return -1;
  }
  memset(lbuf,0,sizeof(lbuf));
  struct sockaddr_in caddr;
  socklen_t caddrlen; 
  caddrlen = (socklen_t)sizeof(struct sockaddr_in);
  int len = recvfrom(dnsk,lbuf,sizeof(lbuf),0,(struct sockaddr*)&caddr,&caddrlen);
  char qname[256];
  n = dnsdecode(lbuf,len,qname,dnsid);
  if(n <= 0) {
    mylog("error:dnsdecode failed\n");
    return -1;
  }
  n = min(olen,n);
  memcpy(out,lbuf,n);
  return n;
}
#endif // MYDNSSTATIC
