diff --git a/bin/if.c b/bin/if.c index fa2eaaa..ac2ee06 100644 --- a/bin/if.c +++ b/bin/if.c @@ -19,12 +19,29 @@ enum mode { MODE_IFOPTS, MODE_IFADDR, MODE_KISS, - MODE_BAUD, - MODE_FLOW + MODE_APRS_IS, +}; + +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) { - enum mode mode = MODE_KISS; + enum mode_kiss mode = MODE_KISS_DEVICE; 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; ierr, "Invalid parameter '%s'", argv[i]); @@ -60,7 +77,7 @@ static patty_ax25_if *create_kiss(struct context *ctx, int argc, char **argv) { break; - case MODE_BAUD: + case MODE_KISS_BAUD: if (!(argv[i][0] >= '0' && argv[i][0] <= '9')) { patty_error_fmt(ctx->err, "Invalid baud rate '%s'", 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.baud = atoi(argv[i]); - mode = MODE_IFOPTS; + mode = MODE_KISS_IFOPTS; break; - case MODE_FLOW: + case MODE_KISS_FLOW: if (strcmp(argv[i], "crtscts") == 0) { info.flags |= PATTY_KISS_TNC_FLOW; 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; } - mode = MODE_IFOPTS; + mode = MODE_KISS_IFOPTS; break; @@ -107,14 +124,113 @@ error_invalid: 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; ierr, "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 { const char *name; patty_ax25_if *(*func)(struct context *ctx, int argc, char **argv); }; struct if_type if_types[] = { - { "kiss", create_kiss }, - { NULL, NULL } + { "kiss", create_kiss }, + { "aprs-is", create_aprs_is }, + { NULL, NULL } }; patty_ax25_if *patty_bin_if_create(int argc, char **argv, patty_error *e) { diff --git a/include/patty/ax25.h b/include/patty/ax25.h index a67a304..01746d0 100644 --- a/include/patty/ax25.h +++ b/include/patty/ax25.h @@ -104,6 +104,7 @@ typedef struct _patty_ax25_if patty_ax25_if; #include #include #include +#include #include #include #include diff --git a/include/patty/ax25/aprs_is.h b/include/patty/ax25/aprs_is.h new file mode 100644 index 0000000..3b792d1 --- /dev/null +++ b/include/patty/ax25/aprs_is.h @@ -0,0 +1,60 @@ +#ifndef _PATTY_AX25_APRS_IS_H +#define _PATTY_AX25_APRS_IS_H + +#include + +#include + +#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 */ diff --git a/src/Makefile b/src/Makefile index a979dcd..24fd991 100644 --- a/src/Makefile +++ b/src/Makefile @@ -7,12 +7,12 @@ CC = $(CROSS)cc CFLAGS = $(CGFLAGS) -fPIC -Wall -O2 -I$(INCLUDE_PATH) LDFLAGS = -lutil -HEADERS = kiss.h kiss/tnc.h ax25.h client.h ax25/if.h ax25/frame.h \ - ax25/sock.h ax25/route.h ax25/server.h daemon.h \ +HEADERS = kiss.h kiss/tnc.h ax25/aprs_is.h ax25.h client.h ax25/if.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 -OBJS = kiss.o tnc.o ax25.o client.o if.o frame.o \ - sock.o route.o server.o daemon.o \ +OBJS = kiss.o tnc.o aprs_is.o ax25.o client.o if.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 VERSION_MAJOR = 0 diff --git a/src/aprs_is.c b/src/aprs_is.c new file mode 100644 index 0000000..4e6b589 --- /dev/null +++ b/src/aprs_is.c @@ -0,0 +1,349 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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; +};