pentair-protocol/aprs485.c

699 lines
15 KiB
C

/*
* aprs485 - RS485 access point/bridge/hub
*/
#include "aprs485.h"
#include <termios.h>
char version[] = "@(#) aprs485 0.09 20100514";
typedef struct { /* log entry */
tmv_t tim;
char buf[256-8];
} sle_t;
void slog(char *fmt, ...);
void slog_dump();
typedef struct {
tmv_t tim; /* pwr on time */
int pwr;
soa_t adr;
int pks;
u08_t pkb[UDPSIZ];
} tab_t;
struct {
int exit;
tmv_t tboot;
char bus[128]; /* bus adapter (tty or TCP socket) */
int esel; /* consecutive error count select() */
int esin; /* consecutive error count soc_in() */
int dbug;
char *ldir; /* log directory */
int lprd; /* log partial reads on bus */
int nrcv; /* bus receive buffer */
u08_t brcv[UDPSIZ];
tab_t tabs[8]; /* tab clients */
tab_t *tsnd; /* tab waiting to send */
int nsle; /* log mechanism */
sle_t sles[16];
} gl;
/*
* log mechanism
*/
static int tm2yymmdd(struct tm *tm)
{
return (tm->tm_year%100)*10000+(tm->tm_mon+1)*100+tm->tm_mday;
}
void slog(char *fmt, ...)
{
sle_t *se;
int *a, i;
se = &gl.sles[gl.nsle++];
if (gl.nsle >= NEL(gl.sles)) return;
gettimeofday(&se->tim,0);
a = (int *)&fmt; a++;
i = snprintf(se->buf,NEL(se->buf)-1,fmt,a[0],a[1],a[2],a[3],a[4],a[5]);
se->buf[i] = 0;
}
void slog_dump()
{
sle_t *se;
int ymd, lf, fd, k;
char *p, buf[BUFSIZ], lfn[BUFSIZ];
struct tm tm;
fd = lf = -1;
for (se = gl.sles, k = MIN(NEL(gl.sles),gl.nsle); --k >= 0; se++) {
p = buf;
localtime_r(&se->tim.tv_sec,&tm);
ymd = tm2yymmdd(&tm);
p += sprintf(p,"%06d",ymd);
p += sprintf(p," %02d:%02d:%02d.%03lu",tm.tm_hour,tm.tm_min,tm.tm_sec,se->tim.tv_usec/1000);
p += sprintf(p," %.*s",NEL(se->buf),se->buf);
if (p[-1] != '\n' && p[-1] != '\r') *p++ = '\n';
if (gl.ldir) {
if (fd < 0 || lf != ymd) {
if (fd >= 0) close(fd);
lf = ymd;
sprintf(lfn,"%s/%06d.log",gl.ldir,lf);
fd = open(lfn,O_WRONLY|O_CREAT|O_APPEND,0644);
}
if (fd >= 0) write(fd,buf,p-buf);
}
if (gl.dbug) *p++ = '\r', write(1,buf,p-buf);
}
if (fd >= 0) close(fd);
gl.nsle = 0;
}
/*
* tab management
*/
tab_t *soa2tab(soa_t *sa)
{
tab_t *t;
int n;
for (t = gl.tabs, n = NEL(gl.tabs); --n >= 0; t++)
if (t->pwr != 1) continue;
else if (t->adr.sin_addr == sa->sin_addr &&
t->adr.sin_port == sa->sin_port) return t;
return 0;
}
/*
* tab[] client/server communication
* messages are 2 bytes:
*
* client server
* BEL <x> ACK <tab#> client request to attach to hub tab
* NAK <#tabs> error, no tab available
*
* DEL <x> ACK <x> client detach request
* NAK <x> error, not on a tab
*
* EOT 0 server exit
*/
int tab_man(soa_t *sa, u08_t *pkb, int pks)
{
tab_t *t;
int n;
if (pks != 2) return 0;
switch (pkb[0]) {
case BEL: /* request tab */
if ((t = soa2tab(sa)) == 0) {
for (t = gl.tabs, n = NEL(gl.tabs); --n >= 0; t++)
if (t->pwr == 0) {
t->pwr = 1;
t->adr = *sa;
t->pks = 0;
gettimeofday(&t->tim,0);
break;
}
if (n < 0) t = 0;
else slog("T __ add [%d]",t-gl.tabs);
}
pkb[0] = t ? ACK : NAK;
pkb[1] = t ? (t-gl.tabs) : NEL(gl.tabs);
return 2;
break;
case DEL: /* drop tab */
if ((t = soa2tab(sa))) {
if (t == gl.tsnd) gl.tsnd = 0;
t->pwr = 0;
pkb[0] = ACK;
slog("T __ del [%d]",t-gl.tabs);
}
else pkb[0] = NAK;
return 2;
break;
}
return 0;
}
void snd2tabs(int sd, tab_t *ts, u08_t *pkb, int pks)
{
tab_t *t;
int n, k;
if (pks <= 0) return;
for (t = gl.tabs, n = NEL(gl.tabs); --n >= 0; t++)
if (t->pwr == 1 && t != ts) {
k = sendto(sd,pkb,pks,0,&t->adr.sa,sizeof(t->adr.sa));
if (k != pks) {
t->pwr = 0;
slog("T __ drop [%d] %s",t-gl.tabs,k<0?strerror(errno):"");
}
}
}
char *hex(u08_t *b, int nb, char *hb, int nh)
{
char *h = hb;
int i;
for (i = MIN((nh/2-1),nb); --i >= 0; b++) {
*h++ = "0123456789abcdef"[(*b>>4)&0x0f];
*h++ = "0123456789abcdef"[(*b>>0)&0x0f];
}
*h = 0;
return hb;
}
/*
* debug I/O
*/
void usr_in(fd)
{
int k;
char buf[32];
if ((k = read(fd,buf,sizeof(buf))) != 1) {
slog("E __ term read()=%d %s",k,k<0?strerror(errno):"");
gl.exit = 1;
return;
}
switch (buf[0]) {
default: slog("D __ hit <Escape> to exit"); break;
case ESC: gl.exit = 1; break;
case 'D': gl.lprd ^= 1; slog("D __ log partial read %d now",gl.lprd); break;
}
}
/*
* bus I/O
*/
void bus_in(int bd)
{
int k;
char hb[128];
if ((k = NEL(gl.brcv) - gl.nrcv) <= 0) {
slog("E __ bus buffer overrun drop %d bytes",gl.nrcv);
gl.nrcv = 0; /* drop everything */
k = NEL(gl.brcv);
}
if ((k = read(bd,&gl.brcv[gl.nrcv],k)) <= 0) {
slog("E __ bus read()=%d %s",k,k<0?strerror(errno):"disconnect!");
gl.exit = 1;
return;
}
if (gl.lprd) slog("r %2d %s",k,hex(&gl.brcv[gl.nrcv],k,hb,sizeof(hb)));
gl.nrcv += k;
}
void bus_out(int sd, int bd)
{
tab_t *t;
int k;
char hb[UDPSIZ*2+4];
if ((t = gl.tsnd) == 0) return;
slog("S %2d %s",t->pks,hex(t->pkb,t->pks,hb,sizeof(hb)));
if ((k = write(bd,t->pkb,t->pks)) != t->pks) {
slog("E __ bus write(%d)=%d %s",t->pks,k,k<0?strerror(errno):"");
if (k < 0) gl.exit = 1;
}
else snd2tabs(sd,t,t->pkb,t->pks);
t->pks = 0;
gl.tsnd = 0;
}
/*
* client interface
*/
int str2av(char *line, char **av, int nav)
{
char *b, *s, *d, del;
int ac;
for (b = line; *b && *b != '\n' && *b != '\r'; b++);
*b = 0;
for (b = line, ac = 0; ac < nav; ) {
while (*b == ' ' || *b == '\t') b++;
if (*b == 0 || *b == '#') break;
if (*b == '\"') {
del = *b++;
av[ac] = b;
while (*b != 0 && (*b != del || b[-1] == '\\')) b++;
if (*b == del) *b++ = 0; else av[ac] -= 1;
for (s = d = av[ac]; *s && s < b; s++)
if (s > av[ac] && *s == del && d[-1] == '\\') d[-1] = del;
else if (s == d) d++;
else *d++ = *s;
*d = 0;
ac++;
}
else {
av[ac++] = b;
while (*b != 0 && *b != ' ' && *b != '\t') b++;
}
if (*b == 0) break;
*b++ = 0;
}
return ac;
}
void srv_client(int sd, soa_t *fa, int na, char **av)
{
tab_t *t;
int n;
u08_t *u;
char *r, *a, ans[UDPSIZ];
struct tm tm;
a = ans;
if (na <= 0 || !strcmp(av[0],"stat")) {
a += sprintf(a,"%s bus:%s",version+5,gl.bus);
localtime_r(&gl.tboot.tv_sec,&tm);
a += sprintf(a,"\nboot: %04d/%02d/%02d %02d:%02d:%02d",
tm.tm_year+1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec);
for (t = gl.tabs, n = NEL(gl.tabs); --n >= 0; t++) {
if (t->pwr == 0) continue;
a += sprintf(a,"\ntab[%d]",t-gl.tabs);
localtime_r(&t->tim.tv_sec,&tm);
a += sprintf(a," %04d/%02d/%02d %02d:%02d:%02d",
tm.tm_year+1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec);
u = (u08_t *)&t->adr.sin_addr;
a += sprintf(a," %d.%d.%d.%d:%d",u[0],u[1],u[2],u[3],ntohs(t->adr.sin_port));
}
}
else if (!strcmp(av[0],"kill")) {
if (na != 2 || (n = strtol(av[1],&r,0)) < 0 || n >= NEL(gl.tabs) || r == av[1] || *r)
a += sprintf(a,"which one?");
else {
t = &gl.tabs[n];
if (t->pwr == 0) a += sprintf(a,"tab[%d] is not active",n);
else {
sendto(sd,EOT_STR,2,0,&t->adr.sa,sizeof(t->adr.sa));
t->pwr = 0;
slog("T __ kill [%d]",t-gl.tabs);
a += sprintf(a,"done");
}
}
}
else if (!strcmp(av[0],"exit")) {
a += sprintf(a,"bye!");
gl.exit = 1;
}
else if (!strcmp(av[0],"l") || !strcmp(av[0],"logdir")) {
a += sprintf(a,"%s",gl.ldir?gl.ldir:"none");
}
else a += sprintf(a,"what?");
if (a > ans) sendto(sd,ans,a-ans,0,&fa->sa,sizeof(fa->sa));
}
/*
* socket I/O
*/
void soc_in(int sd)
{
tab_t *t;
soa_t fa;
int pks, k;
u08_t pkb[UDPSIZ+4];
char *av[32];
socklen_t len;
len = sizeof(fa.sa);
pks = recvfrom(sd,pkb,sizeof(pkb)-4,0,&fa.sa,&len);
if (pks <= 0) {
if (++gl.esin >= 3) gl.exit = 1;
slog("E __ socket read()=%d %s",pks,pks<0?strerror(errno):"");
return;
}
gl.esin = 0;
if (pks <= 2 && (k = tab_man(&fa,pkb,pks)) > 0) {
sendto(sd,pkb,k,0,&fa.sa,len);
return;
}
if (pkb[0] == 'c' && pks >= 6 && !strncmp((char *)pkb,"client",6)) {
pkb[pks] = 0;
if ((k = str2av((char *)pkb+6,av,NEL(av))) > 0)
srv_client(sd,&fa,k,av);
return;
}
if (gl.tsnd) return; /* busy, drop it */
if ((t = soa2tab(&fa)) == 0) return; /* not registered */
bcopy(pkb,t->pkb,t->pks=pks);
gl.tsnd = t;
}
int a5end(u08_t *pk, int np)
{
pa5_t *p;
u08_t *b, *e;
for (e = (b = pk) + np; b < e && *b != 0xa5; b++);
if (b >= e) return np;
p = (pa5_t *)b;
b = &p->dat[p->len+2];
if (b >= e || *b != 0xff) return np;
return b - pk;
}
void soc_out(int sd)
{
int n, i, k;
char hb[UDPSIZ*2+4];
if ((n = gl.nrcv) > 2) {
for (i = 0; i < n; i += k) {
k = a5end(gl.brcv+i,n-i);
slog("R %2d %s",k,hex(gl.brcv+i,k,hb,sizeof(hb)));
snd2tabs(sd,0,gl.brcv+i,k);
}
}
else slog("E %2d %s noise?",n,hex(gl.brcv,n,hb,sizeof(hb)));
gl.nrcv = 0;
}
/*
* bridge server
*/
#define MTICK (15*60)
#define TBRTO 20000 /* bus read timeout (end of package) [microseconds] */
int bridge(int bd, int sd)
{
fd_set pfds, rfds;
tmv_t tc, tv;
int md, ct, lt, n;
FD_ZERO(&pfds);
if (gl.dbug) FD_SET(0,&pfds);
FD_SET(bd,&pfds);
FD_SET(sd,&pfds);
md = MAX(bd,sd) + 1;
ct = lt = -1;
slog("B __ %s bus:%s",version+5,gl.bus);
while (!gl.exit) {
gettimeofday(&tc,0);
if ((ct = (tc.tv_sec%(60*60))/MTICK) != lt) { /* mark tick */
if (lt >= 0) slog("M __ %s bus:%s",version+5,gl.bus);
lt = ct;
}
slog_dump();
tv.tv_sec = tv.tv_usec = 0;
if (gl.nrcv <= 0 && gl.tsnd == 0) {
tv.tv_sec = ((tc.tv_sec/MTICK)+1)*MTICK;
timersub(&tv,&tc,&tv);
}
if (tv.tv_sec == 0 && tv.tv_usec < TBRTO)
tv.tv_usec = TBRTO;
rfds = pfds;
if ((n = select(md,&rfds,0,0,&tv)) < 0) {
if (++gl.esel >= 3) gl.exit = 1;
slog("E __ select() %s",strerror(errno));
continue;
}
gl.esel = 0;
if (n == 0) {
if (gl.nrcv > 0) soc_out(sd);
else if (gl.tsnd) bus_out(sd,bd);
}
else {
if (gl.dbug && FD_ISSET(0,&rfds)) usr_in(0);
if (FD_ISSET(bd,&rfds)) bus_in(bd);
if (FD_ISSET(sd,&rfds)) soc_in(sd);
}
}
snd2tabs(sd,0,(u08_t *)EOT_STR,2);
slog("X __ %s bus:%s",version+5,gl.bus);
slog_dump();
return 0;
}
int open_aptty(char *dev)
{
int bd, eno;
struct termios tio;
if ((bd = open(dev,O_RDWR|O_NOCTTY|O_NDELAY)) < 0)
return -1;
while (1) {
tcflush(bd,TCIOFLUSH);
if (tcgetattr(bd,&tio) < 0) break;
tio.c_iflag = IGNPAR;
tio.c_oflag = 0;
tio.c_cflag = CREAD|CLOCAL|B9600|CS8;
tio.c_lflag = 0;
tio.c_line = 0;
tio.c_cc[VMIN] = 0;
tio.c_cc[VTIME] = 0;
if (tcsetattr(bd,TCSANOW,&tio) < 0) break;
return bd;
}
eno = errno;
close(bd);
errno = eno;
return -1;
}
int open_apsoc(char *ip, char *err)
{
soa_t sa;
int bd, i;
char *p, *r, buf[BUFSIZ];
struct hostent *h;
if (ip == 0 || strlen(ip) >= NEL(buf)) {
if (err) sprintf(err,"name too long");
return -1;
}
strcpy(buf,ip);
bzero(&sa,sizeof(sa));
sa.sin_fmly = AF_INET;
if ((p = strrchr(buf,':')) == 0) {
if (err) sprintf(err,"port not specified");
return -1;
}
*p++ = 0;
i = strtol(p,&r,10);
if (r <= p || *r || i <= 0 || i >= 0xffff) {
if (err) sprintf(err,"bad port '%s'",p);
return -1;
}
sa.sin_port = htons(i);
if (*(p = buf) == 0) p = "localhost";
if ((h = gethostbyname(p)) == 0) {
if (err) sprintf(err,"%s",hstrerror(h_errno));
return -1;
}
bcopy(h->h_addr_list[0],&sa.sin_addr,h->h_length);
if ((bd = socket(sa.sin_fmly,SOCK_STREAM,0)) < 0) {
if (err) sprintf(err,"%s",strerror(errno));
return -1;
}
if (connect(bd,&sa.sa,sizeof(sa.sa))) {
if (err) sprintf(err,"%s",strerror(errno));
close(bd);
return -1;
}
fcntl(bd,F_SETFL,O_NONBLOCK);
return bd;
}
#include <signal.h>
void sighandler(int sig)
{
switch (sig) {
case SIGHUP:
case SIGTERM: gl.exit++; break;
}
}
void sigsetup()
{
struct sigaction action;
sigaction(SIGHUP,NULL,&action);
action.sa_handler = sighandler;
action.sa_flags = 0;
sigaction(SIGHUP, &action,0);
sigaction(SIGTERM,&action,0);
sigaction(SIGALRM,&action,0);
}
int av2str(int na, char **av, char *str, int sob)
{
char *s, *d, *e;
int i;
if ((d = str) && sob > 0) {
for (e = d + sob - 1, i = 0; i < na && d < e; i++) {
if (i) *d++ = ' ';
for (s = av[i]; d < e && (*d = *s++); d++);
}
*d = 0;
}
return d - str;
}
int client(int sd, soa_t *sa, int ac, char **av)
{
fd_set rds;
tmv_t tv;
int n;
char buf[UDPSIZ];
n = sprintf(buf,"client ");
if (ac <= 0) n += sprintf(buf+n,"stat");
else n += av2str(ac,av,buf+n,sizeof(buf)-1-n);
if (sendto(sd,buf,n,0,&sa->sa,sizeof(sa->sa)) != n) return errno;
FD_ZERO(&rds);
FD_SET(sd,&rds);
tv.tv_sec = 1; tv.tv_usec = 0;
if ((n = select(sd+1,&rds,0,0,&tv)) < 0) return errno;
if (n == 0) return ETIME;
if ((n = read(sd,buf,sizeof(buf))) < 0) return errno;
printf("%.*s\n",n,buf);
return 0;
}
int main(int ac, char **av)
{
char *p;
soa_t sa;
int bd, sd, k, eno;
struct termios tio[2];
bzero(&gl,sizeof(gl));
gettimeofday(&gl.tboot,0);
bzero(&sa,sizeof(sa));
sa.sin_fmly = AF_INET;
sa.sin_port = htons(APRS485_PORT);
for (k = 0; ++k < ac; )
if (*(p = av[k]) == '-')
switch (*(++p)) {
default:
USAGE: printf("usage: [-<options>] <bus>\n");
printf("-p <port> - server port (default: %d)\n",APRS485_PORT);
printf("-l <logdir> - log directory\n");
printf("-d - debug, don't fork\n");
printf("-D - log partial reads from <bus>\n");
printf("<bus> - <ttydev> or <ipaddr:port>\n");
printf("-c <cmd ..> - send <cmd> to running server\n");
return EINVAL;
break;
case 'd': gl.dbug++; break;
case 'D': gl.lprd++; break;
case 'c':
if ((sd = socket(sa.sin_fmly,SOCK_DGRAM,0)) < 0) {
printf("socket: %s\n",strerror(eno=errno));
return eno;
}
k++;
eno = client(sd,&sa,ac-k,av+k);
close(sd);
if (eno) printf("client: %s\n",strerror(eno));
return 0;
break;
case 'p':
if (++k >= ac) goto USAGE;
eno = strtol(av[k],0,0);
if (eno <= 1024 || eno > 0xffff) goto USAGE;
sa.sin_port = htons(eno);
break;
case 'l':
if (++k >= ac) goto USAGE;
gl.ldir = av[k];
break;
}
else if (gl.bus[0]) goto USAGE;
else if (*p == 'S' || (*p >= '0' && *p <= '9'))
sprintf(gl.bus,"/dev/tty%s",p);
else sprintf(gl.bus,"%.*s",sizeof(gl.bus)-1,p);
if (gl.bus[0] == 0) goto USAGE;
if (tcgetattr(0,&tio[0]) < 0) {
printf("tcgetattr: %s\n",strerror(eno=errno));
return eno;
}
if ((sd = socket(sa.sin_fmly,SOCK_DGRAM,0)) <= 0) {
printf("socket: %s\n",strerror(eno=errno));
return eno;
}
if (fcntl(sd,F_SETFL,O_NONBLOCK)) {
printf("fcntl(O_NONBLOCK): %s\n",strerror(eno=errno));
return eno;
}
if (bind(sd,&sa.sa,sizeof(sa.sa))) {
printf("bind to port %d: %s\n",ntohs(sa.sin_port),strerror(eno=errno));
return eno;
}
k = 1; setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&k,sizeof(k));
if (strchr(gl.bus,':')) {
char msg[128];
if ((bd = open_apsoc(gl.bus,msg)) <= 0) {
printf("%s: %s\n",gl.bus,msg);
close(sd);
return EINVAL;
}
}
else if ((bd = open_aptty(gl.bus)) <= 0) {
printf("%s: %s\n",gl.bus,strerror(eno=errno));
close(sd);
return eno;
}
if (!gl.dbug) {
if ((k = fork()) < 0) {
printf("fork: %s\n",strerror(eno=errno));
close(bd);
close(sd);
return eno;
}
if (k > 0) return 0; /* parent, go away */
/* child continues */
setsid();
close(0);
close(1);
close(2);
}
else {
tio[1] = tio[0];
cfmakeraw(&tio[1]);
tcsetattr(0,TCSANOW,&tio[1]);
printf("hit <Escape> to exit...\r\n");
}
sigsetup();
eno = bridge(bd,sd);
if (gl.dbug) tcsetattr(0,TCSANOW,tio);
close(bd);
close(sd);
return eno;
}