Implement APRS-IS interface type

Implement an APRS-IS interface type which is capable of receiving all
APRS-IS traffic which can be represented in AX.25 frames; currently,
there are no provisions for handling extended features such as
non-numeric SSIDs, nor a client nor server which facilitate direct
APRS-IS communication.  Presently, APRS-IS traffic cannot be sent from
this interface.

Changes:

    * Add patty_ax25_aprs_is driver type in src/aprs_is.c

    * Implement 'aprs-is' interface configuration type in bin/if.c; make
      separate, smaller state machines for handling base interface
      config, KISS, and APRS-IS interface config
This commit is contained in:
XANTRONIX Development 2020-09-14 00:24:59 -04:00 committed by XANTRONIX Industrial
parent 29fbea2e18
commit a807a9ec9d
5 changed files with 544 additions and 18 deletions

144
bin/if.c
View file

@ -19,12 +19,29 @@ enum mode {
MODE_IFOPTS, MODE_IFOPTS,
MODE_IFADDR, MODE_IFADDR,
MODE_KISS, MODE_KISS,
MODE_BAUD, MODE_APRS_IS,
MODE_FLOW };
enum mode_kiss {
MODE_KISS_DEVICE,
MODE_KISS_IFOPTS,
MODE_KISS_BAUD,
MODE_KISS_FLOW,
};
enum mode_aprs_is {
MODE_APRS_IS_IFOPTS,
MODE_APRS_IS_HOST,
MODE_APRS_IS_PORT,
MODE_APRS_IS_USER,
MODE_APRS_IS_PASS,
MODE_APRS_IS_APPNAME,
MODE_APRS_IS_VERSION,
MODE_APRS_IS_FILTER,
}; };
static patty_ax25_if *create_kiss(struct context *ctx, int argc, char **argv) { static patty_ax25_if *create_kiss(struct context *ctx, int argc, char **argv) {
enum mode mode = MODE_KISS; enum mode_kiss mode = MODE_KISS_DEVICE;
patty_kiss_tnc_info info; patty_kiss_tnc_info info;
@ -40,19 +57,19 @@ static patty_ax25_if *create_kiss(struct context *ctx, int argc, char **argv) {
for (i=0; i<argc; i++) { for (i=0; i<argc; i++) {
switch (mode) { switch (mode) {
case MODE_KISS: case MODE_KISS_DEVICE:
info.flags |= PATTY_KISS_TNC_DEVICE; info.flags |= PATTY_KISS_TNC_DEVICE;
info.device = argv[i]; info.device = argv[i];
mode = MODE_IFOPTS; mode = MODE_KISS_IFOPTS;
break; break;
case MODE_IFOPTS: case MODE_KISS_IFOPTS:
if (strcmp(argv[i], "baud") == 0) { if (strcmp(argv[i], "baud") == 0) {
mode = MODE_BAUD; mode = MODE_KISS_BAUD;
} else if (strcmp(argv[i], "flow") == 0) { } else if (strcmp(argv[i], "flow") == 0) {
mode = MODE_FLOW; mode = MODE_KISS_FLOW;
} else { } else {
patty_error_fmt(ctx->err, "Invalid parameter '%s'", patty_error_fmt(ctx->err, "Invalid parameter '%s'",
argv[i]); argv[i]);
@ -60,7 +77,7 @@ static patty_ax25_if *create_kiss(struct context *ctx, int argc, char **argv) {
break; break;
case MODE_BAUD: case MODE_KISS_BAUD:
if (!(argv[i][0] >= '0' && argv[i][0] <= '9')) { if (!(argv[i][0] >= '0' && argv[i][0] <= '9')) {
patty_error_fmt(ctx->err, "Invalid baud rate '%s'", patty_error_fmt(ctx->err, "Invalid baud rate '%s'",
argv[i]); argv[i]);
@ -71,11 +88,11 @@ static patty_ax25_if *create_kiss(struct context *ctx, int argc, char **argv) {
info.flags |= PATTY_KISS_TNC_BAUD; info.flags |= PATTY_KISS_TNC_BAUD;
info.baud = atoi(argv[i]); info.baud = atoi(argv[i]);
mode = MODE_IFOPTS; mode = MODE_KISS_IFOPTS;
break; break;
case MODE_FLOW: case MODE_KISS_FLOW:
if (strcmp(argv[i], "crtscts") == 0) { if (strcmp(argv[i], "crtscts") == 0) {
info.flags |= PATTY_KISS_TNC_FLOW; info.flags |= PATTY_KISS_TNC_FLOW;
info.flow = PATTY_KISS_TNC_FLOW_CRTSCTS; info.flow = PATTY_KISS_TNC_FLOW_CRTSCTS;
@ -89,7 +106,7 @@ static patty_ax25_if *create_kiss(struct context *ctx, int argc, char **argv) {
goto error_invalid; goto error_invalid;
} }
mode = MODE_IFOPTS; mode = MODE_KISS_IFOPTS;
break; break;
@ -107,14 +124,113 @@ error_invalid:
return NULL; return NULL;
} }
static patty_ax25_if *create_aprs_is(struct context *ctx,
int argc,
char **argv) {
enum mode_aprs_is mode = MODE_APRS_IS_IFOPTS;
patty_ax25_aprs_is_info info = {
.user = "N0CALL",
.pass = "-1",
.appname = PATTY_AX25_APRS_IS_DEFAULT_APPNAME,
.version = PATTY_AX25_APRS_IS_DEFAULT_VERSION,
.filter = "m/25"
};
int i;
if (argc == 0) {
patty_error_fmt(ctx->err, "Too few parameters for 'kiss' interface");
goto error_invalid;
}
for (i=0; i<argc; i++) {
switch (mode) {
case MODE_APRS_IS_IFOPTS:
if (strcmp(argv[i], "host") == 0) {
mode = MODE_APRS_IS_HOST;
} else if (strcmp(argv[i], "port") == 0) {
mode = MODE_APRS_IS_PORT;
} else if (strcmp(argv[i], "user") == 0) {
mode = MODE_APRS_IS_USER;
} else if (strcmp(argv[i], "pass") == 0) {
mode = MODE_APRS_IS_PASS;
} else if (strcmp(argv[i], "appname") == 0) {
mode = MODE_APRS_IS_APPNAME;
} else if (strcmp(argv[i], "version") == 0) {
mode = MODE_APRS_IS_VERSION;
} else if (strcmp(argv[i], "filter") == 0) {
mode = MODE_APRS_IS_FILTER;
} else {
patty_error_fmt(ctx->err, "Invalid parameter '%s'",
argv[i]);
}
break;
case MODE_APRS_IS_HOST:
info.host = argv[i];
mode = MODE_APRS_IS_IFOPTS;
break;
case MODE_APRS_IS_PORT:
info.port = argv[i];
mode = MODE_APRS_IS_IFOPTS;
break;
case MODE_APRS_IS_USER:
info.user = argv[i];
mode = MODE_APRS_IS_IFOPTS;
break;
case MODE_APRS_IS_PASS:
info.pass = argv[i];
mode = MODE_APRS_IS_IFOPTS;
break;
case MODE_APRS_IS_APPNAME:
info.appname = argv[i];
mode = MODE_APRS_IS_IFOPTS;
break;
case MODE_APRS_IS_VERSION:
info.version = argv[i];
mode = MODE_APRS_IS_IFOPTS;
break;
case MODE_APRS_IS_FILTER:
info.filter = argv[i];
mode = MODE_APRS_IS_IFOPTS;
break;
}
}
return patty_ax25_if_new( ctx->name,
&ctx->addr,
patty_ax25_aprs_is_driver(),
&info);
error_invalid:
return NULL;
}
struct if_type { struct if_type {
const char *name; const char *name;
patty_ax25_if *(*func)(struct context *ctx, int argc, char **argv); patty_ax25_if *(*func)(struct context *ctx, int argc, char **argv);
}; };
struct if_type if_types[] = { struct if_type if_types[] = {
{ "kiss", create_kiss }, { "kiss", create_kiss },
{ NULL, NULL } { "aprs-is", create_aprs_is },
{ NULL, NULL }
}; };
patty_ax25_if *patty_bin_if_create(int argc, char **argv, patty_error *e) { patty_ax25_if *patty_bin_if_create(int argc, char **argv, patty_error *e) {

View file

@ -104,6 +104,7 @@ typedef struct _patty_ax25_if patty_ax25_if;
#include <patty/ax25/frame.h> #include <patty/ax25/frame.h>
#include <patty/ax25/if.h> #include <patty/ax25/if.h>
#include <patty/kiss/tnc.h> #include <patty/kiss/tnc.h>
#include <patty/ax25/aprs_is.h>
#include <patty/ax25/route.h> #include <patty/ax25/route.h>
#include <patty/ax25/sock.h> #include <patty/ax25/sock.h>
#include <patty/ax25/server.h> #include <patty/ax25/server.h>

View file

@ -0,0 +1,60 @@
#ifndef _PATTY_AX25_APRS_IS_H
#define _PATTY_AX25_APRS_IS_H
#include <stdint.h>
#include <patty/ax25.h>
#define PATTY_AX25_APRS_IS_DEFAULT_APPNAME "patty-aprs-is"
#define PATTY_AX25_APRS_IS_DEFAULT_VERSION "0.0.0"
#define PATTY_AX25_APRS_IS_PAYLOAD_MAX 256
#define PATTY_AX25_APRS_IS_PACKET_MAX 512
#define PATTY_AX25_APRS_IS_FRAME_MAX PATTY_AX25_APRS_IS_PACKET_MAX
typedef struct _patty_ax25_aprs_is patty_ax25_aprs_is;
typedef struct _patty_ax25_aprs_is_info {
const char *host,
*port,
*user,
*pass,
*appname,
*version,
*filter;
} patty_ax25_aprs_is_info;
ssize_t patty_ax25_aprs_is_encode(void *dest,
const char *src,
size_t len,
size_t size);
patty_ax25_aprs_is *patty_ax25_aprs_is_new(patty_ax25_aprs_is_info *info);
void patty_ax25_aprs_is_destroy(patty_ax25_aprs_is *aprs);
ssize_t patty_ax25_aprs_is_readline(patty_ax25_aprs_is *aprs,
void *buf,
size_t len);
int patty_ax25_aprs_is_printf(patty_ax25_aprs_is *aprs, const char *fmt, ...);
int patty_ax25_aprs_is_fd(patty_ax25_aprs_is *aprs);
ssize_t patty_ax25_aprs_is_pending(patty_ax25_aprs_is *aprs);
ssize_t patty_ax25_aprs_is_fill(patty_ax25_aprs_is *aprs);
ssize_t patty_ax25_aprs_is_drain(patty_ax25_aprs_is *, void *buf, size_t len);
int patty_ax25_aprs_is_ready(patty_ax25_aprs_is *);
ssize_t patty_ax25_aprs_is_flush(patty_ax25_aprs_is *);
ssize_t patty_ax25_aprs_is_send(patty_ax25_aprs_is *aprs,
const void *buf,
size_t len);
patty_ax25_if_driver *patty_ax25_aprs_is_driver();
#endif /* _PATTY_AX25_APRS_IS_H */

View file

@ -7,12 +7,12 @@ CC = $(CROSS)cc
CFLAGS = $(CGFLAGS) -fPIC -Wall -O2 -I$(INCLUDE_PATH) CFLAGS = $(CGFLAGS) -fPIC -Wall -O2 -I$(INCLUDE_PATH)
LDFLAGS = -lutil LDFLAGS = -lutil
HEADERS = kiss.h kiss/tnc.h ax25.h client.h ax25/if.h ax25/frame.h \ HEADERS = kiss.h kiss/tnc.h ax25/aprs_is.h ax25.h client.h ax25/if.h \
ax25/sock.h ax25/route.h ax25/server.h daemon.h \ ax25/frame.h ax25/sock.h ax25/route.h ax25/server.h daemon.h \
error.h list.h hash.h dict.h timer.h print.h conf.h error.h list.h hash.h dict.h timer.h print.h conf.h
OBJS = kiss.o tnc.o ax25.o client.o if.o frame.o \ OBJS = kiss.o tnc.o aprs_is.o ax25.o client.o if.o \
sock.o route.o server.o daemon.o \ frame.o sock.o route.o server.o daemon.o \
error.o list.o hash.o dict.o timer.o print.o conf.o error.o list.o hash.o dict.o timer.o print.o conf.o
VERSION_MAJOR = 0 VERSION_MAJOR = 0

349
src/aprs_is.c Normal file
View file

@ -0,0 +1,349 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <patty/ax25/aprs_is.h>
enum state {
APRS_IS_HEADER,
APRS_IS_COMMENT,
APRS_IS_BODY,
APRS_IS_COMPLETE
};
struct _patty_ax25_aprs_is {
int fd;
char rx_buf[PATTY_AX25_APRS_IS_PACKET_MAX],
tx_buf[PATTY_AX25_APRS_IS_FRAME_MAX],
station[PATTY_AX25_ADDRSTRLEN+1],
info[PATTY_AX25_APRS_IS_PAYLOAD_MAX];
patty_ax25_frame frame;
size_t rx_bufsz,
tx_bufsz,
infosz;
enum state state;
size_t offset_i,
offset_station,
offset_info;
ssize_t readlen,
encoded;
};
static ssize_t send_line(patty_ax25_aprs_is *aprs, char *buf, size_t len) {
if (len > PATTY_AX25_APRS_IS_PACKET_MAX - 2) {
len = PATTY_AX25_APRS_IS_PACKET_MAX - 2;
}
buf[len] = '\r';
buf[len+1] = '\n';
return write(aprs->fd, buf, len+2);
}
static int send_va(patty_ax25_aprs_is *aprs, const char *fmt, va_list args) {
size_t len;
if (vsnprintf(aprs->tx_buf, PATTY_AX25_APRS_IS_PACKET_MAX, fmt, args) < 0) {
goto error_vsnprintf;
}
len = strnlen(aprs->tx_buf, PATTY_AX25_APRS_IS_PACKET_MAX);
if (send_line(aprs, aprs->tx_buf, len) < 0) {
goto error_send_line;
}
return 0;
error_send_line:
error_vsnprintf:
return -1;
}
static int send_fmt(patty_ax25_aprs_is *aprs, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
if (send_va(aprs, fmt, args) < 0) {
goto error_send_va;
}
va_end(args);
return 0;
error_send_va:
return -1;
}
patty_ax25_aprs_is *patty_ax25_aprs_is_new(patty_ax25_aprs_is_info *info) {
patty_ax25_aprs_is *aprs;
struct addrinfo *ai0,
*ai;
if (getaddrinfo(info->host, info->port, NULL, &ai0) < 0) {
goto error_getaddrinfo;
}
if ((aprs = malloc(sizeof(*aprs))) == NULL) {
goto error_malloc_aprs;
}
memset(aprs, '\0', sizeof(*aprs));
aprs->rx_bufsz = PATTY_AX25_APRS_IS_PACKET_MAX;
aprs->tx_bufsz = PATTY_AX25_APRS_IS_FRAME_MAX;
aprs->infosz = PATTY_AX25_APRS_IS_PAYLOAD_MAX;
aprs->state = APRS_IS_HEADER;
for (ai=ai0; ai; ai=ai->ai_next) {
if ((aprs->fd = socket(ai->ai_family,
ai->ai_socktype,
ai->ai_protocol)) < 0) {
continue;
}
if (connect(aprs->fd, ai->ai_addr, ai->ai_addrlen) < 0) {
close(aprs->fd);
continue;
}
freeaddrinfo(ai0);
if (send_fmt(aprs,
"user %s pass %s vers %s %s filter %s",
info->user,
info->pass,
info->appname,
info->version,
info->filter) < 0) {
goto error_send_fmt;
}
return aprs;
}
error_send_fmt:
close(aprs->fd);
free(aprs);
error_malloc_aprs:
error_getaddrinfo:
return NULL;
}
void patty_ax25_aprs_is_destroy(patty_ax25_aprs_is *aprs) {
(void)close(aprs->fd);
free(aprs);
}
int patty_ax25_aprs_is_fd(patty_ax25_aprs_is *aprs) {
return aprs->fd;
}
ssize_t patty_ax25_aprs_is_pending(patty_ax25_aprs_is *aprs) {
return aprs->offset_i < aprs->readlen? 1: 0;
}
ssize_t patty_ax25_aprs_is_fill(patty_ax25_aprs_is *aprs) {
if ((aprs->readlen = read(aprs->fd, aprs->rx_buf, aprs->rx_bufsz)) < 0) {
goto error_read;
}
aprs->offset_i = 0;
return aprs->readlen;
error_read:
return -1;
}
ssize_t patty_ax25_aprs_is_drain(patty_ax25_aprs_is *aprs,
void *buf,
size_t len) {
ssize_t offset_start = aprs->offset_i;
while (aprs->offset_i < aprs->readlen) {
char c = aprs->rx_buf[aprs->offset_i++];
switch (aprs->state) {
case APRS_IS_HEADER: {
patty_ax25_addr *addr = NULL;
if (c == '#') {
aprs->state = APRS_IS_COMMENT;
} else if (c == '>') {
addr = &aprs->frame.src;
} else if (c == ',' || c == ':') {
if (aprs->frame.dest.callsign[0]) {
if (aprs->frame.hops == PATTY_AX25_MAX_HOPS) {
errno = EOVERFLOW;
goto error;
}
addr = &aprs->frame.repeaters[aprs->frame.hops++];
} else {
addr = &aprs->frame.dest;
}
if (c == ':') {
aprs->state = APRS_IS_BODY;
}
} else if (PATTY_AX25_ADDR_CHAR_VALID(c)) {
if (aprs->offset_station == PATTY_AX25_ADDRSTRLEN) {
errno = EOVERFLOW;
goto error;
}
aprs->station[aprs->offset_station++] = c;
} else {
errno = EINVAL;
goto error;
}
if (addr) {
aprs->station[aprs->offset_station] = '\0';
if (patty_ax25_pton(aprs->station, addr) < 0) {
(void)patty_ax25_aprs_is_flush(aprs);
goto done;
}
aprs->offset_station = 0;
}
break;
}
case APRS_IS_COMMENT:
if (c == '\r') {
break;
} else if (c == '\n') {
aprs->state = APRS_IS_HEADER;
goto done;
}
break;
case APRS_IS_BODY:
if (c == '\r') {
break;
} else if (c == '\n') {
aprs->state = APRS_IS_COMPLETE;
aprs->frame.control = PATTY_AX25_FRAME_UI;
aprs->frame.cr = PATTY_AX25_FRAME_COMMAND;
aprs->frame.proto = PATTY_AX25_PROTO_NONE;
aprs->frame.info = aprs->info;
aprs->frame.infolen = aprs->offset_info;
if ((aprs->encoded = patty_ax25_frame_encode(&aprs->frame,
buf,
len)) < 0) {
goto error;
}
goto done;
} else {
if (aprs->offset_info == aprs->infosz) {
errno = EOVERFLOW;
goto error;
}
aprs->info[aprs->offset_info++] = c;
}
break;
case APRS_IS_COMPLETE:
goto done;
}
}
done:
return aprs->offset_i - offset_start;
error:
return -1;
}
int patty_ax25_aprs_is_ready(patty_ax25_aprs_is *aprs) {
return aprs->state == APRS_IS_COMPLETE? 1: 0;
}
ssize_t patty_ax25_aprs_is_flush(patty_ax25_aprs_is *aprs) {
ssize_t ret = aprs->encoded;
aprs->state = APRS_IS_HEADER;
aprs->offset_i = aprs->readlen;
aprs->offset_station = 0;
aprs->offset_info = 0;
aprs->encoded = 0;
memset(&aprs->frame, '\0', sizeof(aprs->frame));
return ret;
}
int patty_ax25_aprs_is_printf(patty_ax25_aprs_is *aprs, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
if (send_va(aprs, fmt, args) < 0) {
goto error_send_va;
}
va_end(args);
return 0;
error_send_va:
va_end(args);
return -1;
}
ssize_t patty_ax25_aprs_is_send(patty_ax25_aprs_is *aprs,
const void *buf,
size_t len) {
return 0;
}
patty_ax25_if_driver *patty_ax25_aprs_is_driver() {
static patty_ax25_if_driver driver = {
.create = (patty_ax25_if_driver_create *)patty_ax25_aprs_is_new,
.destroy = (patty_ax25_if_driver_destroy *)patty_ax25_aprs_is_destroy,
.fd = (patty_ax25_if_driver_fd *)patty_ax25_aprs_is_fd,
.pending = (patty_ax25_if_driver_pending *)patty_ax25_aprs_is_pending,
.fill = (patty_ax25_if_driver_fill *)patty_ax25_aprs_is_fill,
.drain = (patty_ax25_if_driver_drain *)patty_ax25_aprs_is_drain,
.ready = (patty_ax25_if_driver_ready *)patty_ax25_aprs_is_ready,
.flush = (patty_ax25_if_driver_flush *)patty_ax25_aprs_is_flush,
.send = (patty_ax25_if_driver_send *)patty_ax25_aprs_is_send
};
return &driver;
};