diff --git a/acars.c b/acars.c index 0e3dc9e..3d8f00a 100644 --- a/acars.c +++ b/acars.c @@ -18,6 +18,7 @@ #include #include #include "acarsdec.h" +#include "output.h" #define SYN 0x16 #define SOH 0x01 diff --git a/acarsdec.c b/acarsdec.c index 8f57582..32b25ba 100644 --- a/acarsdec.c +++ b/acarsdec.c @@ -30,11 +30,11 @@ #include #endif #include "acarsdec.h" +#include "output.h" + extern void build_label_filter(char *arg); runtime_t R = { - .outtype = OUTTYPE_STD, - .netout = NETLOG_NONE, .mdly = 600, .rateMult = 160, .lnaState = 2, @@ -48,13 +48,10 @@ static void usage(void) #ifdef HAVE_LIBACARS fprintf(stderr, "(libacars %s)\n", LA_VERSION); #endif - fprintf(stderr, "\nUsage: acarsdec [-o lv] [-t time] [-A] [-b 'labels,..'] [-e] [-i station_id] [-n|-j|-N ipaddr:port] [-l logfile [-H|-D]]"); + fprintf(stderr, "\nUsage: acarsdec [-t time] [-A] [-b 'labels,..'] [-e] [-i station_id]"); #ifdef HAVE_LIBACARS fprintf(stderr, " [--skip-reassembly] "); #endif -#ifdef WITH_MQTT - fprintf(stderr, " [ -M mqtt_url [-T mqtt_topic] | [-U mqtt_user | -P mqtt_passwd]] |"); -#endif #ifdef WITH_ALSA fprintf(stderr, " -a alsapcmdevice |"); #endif @@ -82,27 +79,10 @@ static void usage(void) " -A\t\t\t: don't output uplink messages (ie : only aircraft messages)\n" " -e\t\t\t: don't output empty messages (ie : _d,Q0, etc ...)\n" " -b filter\t\t: filter output by label (ex: -b \"H1:Q0\" : only output messages with label H1 or Q0)\n" - " -o lv\t\t\t: output format : 0 : no log, 1 : one line by msg, 2 : full (default) , 3 : monitor" -#ifdef HAVE_CJSON - ", 4 : msg JSON, 5 : route JSON" -#endif "\n" " -t time\t\t: set forget time (TTL) in seconds for monitor mode (default=600s)\n" - " -l logfile\t\t: append log messages to logfile (Default : stdout).\n" " -H\t\t\t: rotate log file once every hour\n" " -D\t\t\t: rotate log file once every day\n" - "\n" - " -n ipaddr:port\t\t: send acars messages to addr:port on UDP in planeplotter compatible format\n" - " -N ipaddr:port\t\t: send acars messages to addr:port on UDP in acarsdec native format\n" -#ifdef HAVE_CJSON - " -j ipaddr:port\t\t: send acars messages to addr:port on UDP in acarsdec json format\n" -#ifdef WITH_MQTT - " -M mqtt_url\t\t: Url of MQTT broker\n" - " -T mqtt_topic\t\t: Optionnal MQTT topic (default : acarsdec/${station_id})\n" - " -U mqtt_user\t\t: Optional MQTT username\n" - " -P mqtt_passwd\t\t: Optional MQTT password\n" -#endif /* WITH_MQTT */ -#endif /* HAVE_CJSON */ "\n"); #ifdef WITH_ALSA @@ -170,6 +150,7 @@ int main(int argc, char **argv) { "verbose", no_argument, NULL, 'v' }, { "skip-reassembly", no_argument, NULL, 1 }, { "antenna", required_argument, NULL, 2 }, + { "output", required_argument, NULL, 3 }, { NULL, 0, NULL, 0 } }; char sys_hostname[HOST_NAME_MAX + 1]; @@ -180,14 +161,14 @@ int main(int argc, char **argv) R.idstation = strdup(sys_hostname); res = 0; - while ((c = getopt_long(argc, argv, "HDvarfdsRo:t:g:m:Aep:n:N:j:l:c:i:L:G:b:M:P:U:T:B:", long_opts, NULL)) != EOF) { + while ((c = getopt_long(argc, argv, "HDvarfdsRt:g:m:Aep:c:i:L:G:b:B:", long_opts, NULL)) != EOF) { switch (c) { + case 3: + setup_output(optarg); + break; case 'v': R.verbose = 1; break; - case 'o': - R.outtype = atoi(optarg); - break; case 't': R.mdly = atoi(optarg); break; @@ -270,39 +251,6 @@ int main(int argc, char **argv) res = initAirspy(argv, optind); R.inmode = 4; break; -#endif -#ifdef WITH_MQTT - case 'M': - if (R.mqtt_nburls < 15) { - R.mqtt_urls[R.mqtt_nburls] = strdup(optarg); - R.mqtt_nburls++; - R.mqtt_urls[R.mqtt_nburls] = NULL; - R.netout = NETLOG_MQTT; - } - break; - case 'U': - R.mqtt_user = strdup(optarg); - break; - case 'P': - R.mqtt_passwd = strdup(optarg); - break; - case 'T': - R.mqtt_topic = strdup(optarg); - break; -#endif - case 'n': - R.Rawaddr = optarg; - R.netout = NETLOG_PLANEPLOTTER; - break; - case 'N': - R.Rawaddr = optarg; - R.netout = NETLOG_NATIVE; - break; -#ifdef HAVE_CJSON - case 'j': - R.Rawaddr = optarg; - R.netout = NETLOG_JSON; - break; #endif case 'A': R.airflt = 1; @@ -310,9 +258,6 @@ int main(int argc, char **argv) case 'e': R.emptymsg = 1; break; - case 'l': - R.logfilename = optarg; - break; case 'H': R.hourly = 1; break; @@ -342,18 +287,10 @@ int main(int argc, char **argv) build_label_filter(lblf); - res = initOutput(R.logfilename, R.Rawaddr); + res = initOutputs(); if (res) errx(res, "Unable to init output\n"); -#ifdef WITH_MQTT - if (R.netout == NETLOG_MQTT) { - res = MQTTinit(R.mqtt_urls, R.idstation, R.mqtt_topic, R.mqtt_user, R.mqtt_passwd); - if (res) - errx(res, "Unable to init MQTT\n"); - } -#endif - #ifdef WITH_SOAPY if (R.antenna) { if (R.verbose) @@ -444,8 +381,7 @@ int main(int argc, char **argv) deinitAcars(); -#ifdef WITH_MQTT - MQTTend(); -#endif + exitOutputs(); + exit(res); } diff --git a/acarsdec.h b/acarsdec.h index 3f6ec5d..fe8fa4e 100644 --- a/acarsdec.h +++ b/acarsdec.h @@ -16,6 +16,10 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ + +#ifndef acarsdec_h +#define acarsdec_h + #include #include #include @@ -33,18 +37,7 @@ #define INTRATE 12500 -#define NETLOG_NONE 0 -#define NETLOG_PLANEPLOTTER 1 -#define NETLOG_NATIVE 2 -#define NETLOG_JSON 3 -#define NETLOG_MQTT 4 - -#define OUTTYPE_NONE 0 -#define OUTTYPE_ONELINE 1 -#define OUTTYPE_STD 2 -#define OUTTYPE_MONITOR 3 -#define OUTTYPE_JSON 4 -#define OUTTYPE_ROUTEJSON 5 +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) typedef float sample_t; @@ -122,14 +115,20 @@ typedef struct { #endif } acarsmsg_t; +typedef struct output_s { + enum { FMT_ONELINE = 1, FMT_FULL, FMT_MONITOR, FMT_PP, FMT_NATIVE, FMT_JSON, FMT_ROUTEJSON } fmt; + enum { DST_FILE = 1, DST_UDP, DST_MQTT } dst; + void *params; + void *priv; + struct output_s *next; +} output_t; + typedef struct { channel_t *channels; unsigned int nbch; int inmode; int verbose; - int outtype; - int netout; int airflt; int emptymsg; int mdly; @@ -153,22 +152,11 @@ typedef struct { int freq; #endif -#ifdef WITH_MQTT - char *mqtt_urls[16]; - int mqtt_nburls; - char *mqtt_topic; - char *mqtt_user; - char *mqtt_passwd; -#endif - - char *Rawaddr; - char *logfilename; + output_t *outputs; } runtime_t; extern runtime_t R; -extern int initOutput(char *, char *); - #ifdef WITH_ALSA extern int initAlsa(char **argv, int optind); extern int runAlsaSample(void); @@ -198,12 +186,6 @@ extern int runSoapySample(void); extern int runSoapyClose(void); #endif -#ifdef WITH_MQTT -extern int MQTTinit(char **urls, char *client_id, char *topic, char *user, char *passwd); -extern int MQTTsend(char *msgtxt); -extern void MQTTend(); -#endif - extern int initRaw(char **argv, int optind); extern int runRawSample(void); extern int initMsk(channel_t *); @@ -215,4 +197,4 @@ extern int deinitAcars(void); extern int DecodeLabel(acarsmsg_t *msg, oooi_t *oooi); -extern void outputmsg(const msgblk_t *); +#endif /* acarsdec_h */ diff --git a/fileout.c b/fileout.c index 5b5189e..060ca59 100644 --- a/fileout.c +++ b/fileout.c @@ -1,3 +1,23 @@ +/* + * Copyright (c) 2015 Thierry Leconte + * Copyright (c) 2024 Thibaut VARENE + * + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License version 2 + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + #include #include #include @@ -7,24 +27,20 @@ #include #include #include +#include #include "acarsdec.h" +#include "fileout.h" -static char *filename_prefix = NULL; -static char *extension = NULL; -static size_t prefix_len; -static struct tm current_tm; - -static FILE *open_outfile() +static FILE *open_outfile(fileout_t *fout) { char *filename = NULL; char *fmt = NULL; size_t tlen = 0; - FILE *fd; if (R.hourly || R.daily) { time_t t = time(NULL); - gmtime_r(&t, ¤t_tm); + gmtime_r(&t, &fout->current_tm); char suffix[16]; if (R.hourly) @@ -32,69 +48,108 @@ static FILE *open_outfile() else // daily fmt = "_%Y%m%d"; - tlen = strftime(suffix, sizeof(suffix), fmt, ¤t_tm); + tlen = strftime(suffix, sizeof(suffix), fmt, &fout->current_tm); if (tlen == 0) { fprintf(stderr, "*open_outfile(): strfime returned 0\n"); return NULL; } - filename = calloc(prefix_len + tlen + 2, sizeof(char)); + filename = calloc(fout->prefix_len + tlen + 2, sizeof(char)); if (filename == NULL) { fprintf(stderr, "open_outfile(): failed to allocate memory\n"); return NULL; } - sprintf(filename, "%s%s%s", filename_prefix, suffix, extension); + sprintf(filename, "%s%s%s", fout->filename_prefix, suffix, fout->extension); } else { - filename = strdup(filename_prefix); + filename = strdup(fout->filename_prefix); } - if ((fd = fopen(filename, "a+")) == NULL) { + if ((fout->F = fopen(filename, "a+")) == NULL) fprintf(stderr, "Could not open output file %s: %s\n", filename, strerror(errno)); - free(filename); - return NULL; - } + free(filename); - return fd; + return fout->F; } -FILE *Fileoutinit(char *logfilename) +// params: NULL (defaults to stdout) or "path=" followed by "-" for stdtout or full path to file +fileout_t *Fileoutinit(char *params) { - FILE *fd; + char *param, *sep, *path = NULL; + fileout_t *fout; + + while ((param = strsep(¶ms, ","))) { + sep = strchr(param, '='); + if (!sep) + continue; + *sep++ = '\0'; + if (!strcmp("path", param)) + path = sep; + } + + fout = calloc(1, sizeof(*fout)); + if (!fout) + return NULL; - filename_prefix = logfilename; - prefix_len = strlen(filename_prefix); + // params is path or optional "-" for stdout + if (!path || !strcmp("-", path)) { + fout->F = stdout; + return fout; + } + + fout->filename_prefix = path; + fout->prefix_len = strlen(path); if (R.hourly || R.daily) { - char *basename = strrchr(filename_prefix, '/'); + // XXX REVISIT + char *basename = strrchr(path, '/'); if (basename != NULL) basename++; else - basename = filename_prefix; + basename = path; - char *ext = strrchr(filename_prefix, '.'); + char *ext = strrchr(path, '.'); if (ext != NULL && (ext <= basename || ext[1] == '\0')) ext = NULL; - if (ext) { - extension = strdup(ext); - *ext = '\0'; - } else { - extension = strdup(""); - } + fout->extension = ext ? strdup(ext) : strdup(""); } - if ((fd = open_outfile()) == NULL) + + if ((open_outfile(fout)) == NULL) { + free(fout); return NULL; + } - return fd; + return fout; } -FILE *Fileoutrotate(FILE *fd) +static FILE *Fileoutrotate(fileout_t *fout) { struct tm new_tm; time_t t = time(NULL); + gmtime_r(&t, &new_tm); - if ((R.hourly && new_tm.tm_hour != current_tm.tm_hour) || - (R.daily && new_tm.tm_mday != current_tm.tm_mday)) { - fclose(fd); - return open_outfile(); + if ((R.hourly && new_tm.tm_hour != fout->current_tm.tm_hour) || + (R.daily && new_tm.tm_mday != fout->current_tm.tm_mday)) { + fclose(fout->F); + return open_outfile(fout); } - return fd; + return fout->F; +} + +void Filewrite(const char *buf, size_t buflen, fileout_t *fout) +{ + if ((R.hourly || R.daily) && !Fileoutrotate(fout)) + errx(1, "failed to rotate output file %s", fout->filename_prefix); + + fwrite(buf, buflen, 1, fout->F); + fprintf(fout->F, "\n"); + fflush(fout->F); + +} + +void Fileoutexit(fileout_t *fout) +{ + if (stdout != fout->F) + fclose(fout->F); + + free((void *)(uintptr_t)fout->extension); + free(fout); } diff --git a/fileout.h b/fileout.h new file mode 100644 index 0000000..3ceae15 --- /dev/null +++ b/fileout.h @@ -0,0 +1,18 @@ +#ifndef fileout_h +#define fileout_h + +#include + +typedef struct { + FILE *F; + const char *filename_prefix; + const char *extension; + size_t prefix_len; + struct tm current_tm; +} fileout_t; + +fileout_t *Fileoutinit(char *params); +void Filewrite(const char *buf, size_t buflen, fileout_t *fout); +void Fileoutexit(fileout_t *fout); + +#endif /* fileout_h */ diff --git a/mqttout.c b/mqttout.c index f85f1c2..73554e1 100644 --- a/mqttout.c +++ b/mqttout.c @@ -1,24 +1,77 @@ +/* + * Copyright (c) 2015 Thierry Leconte + * Copyright (c) 2024 Thibaut VARENE + * + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License version 2 + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + #include #include #include -#include "MQTTAsync.h" +#include -static MQTTAsync client; -static MQTTAsync_message pubmsg = MQTTAsync_message_initializer; -static char *msgtopic; +#include "acarsdec.h" +#include "mqttout.h" -int MQTTinit(char **urls, char *client_id, char *topic, char *user, char *passwd) +// params is "uri=protocol://host:port,uri=protocol://host:port,user=username,passwd=password,topic=mytopic +mqttout_t *MQTTinit(char *params) { + mqttout_t *mqpriv; + char *urls[15] = {}; + char **url, *topic = NULL, *user = NULL, *passwd = NULL, *msgtopic = NULL; + char *param, *sep; int rc; MQTTAsync_createOptions create_opts = MQTTAsync_createOptions_initializer; MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + url = urls; + while ((param = strsep(¶ms, ","))) { + sep = strchr(param, '='); + if (!sep) + continue; + *sep++ = '\0'; + if (!strcmp("topic", param)) + topic = sep; + else if (!strcmp("user", param)) + user = sep; + else if (!strcmp("passwd", param)) + passwd = sep; + else if (!strcmp("uri", param)) { + if (url > &urls[14]) + fprintf(stderr, "too many MQTT urls provided, ignoring '%s'\n", sep); + else + *url++ = sep; + } + } + + if (!urls[0]) { + fprintf(stderr, "MQTT: no URI provided\n"); + return NULL; + } + create_opts.maxBufferedMessages = 200; create_opts.sendWhileDisconnected = 1; create_opts.allowDisconnectedSendAtAnyTime = 1; create_opts.deleteOldestMessages = 1; - MQTTAsync_createWithOptions(&client, urls[0], client_id, MQTTCLIENT_PERSISTENCE_NONE, NULL, &create_opts); + mqpriv = calloc(1, sizeof(*mqpriv)); + if (!mqpriv) + return NULL; + + MQTTAsync_createWithOptions(&mqpriv->client, urls[0], R.idstation, MQTTCLIENT_PERSISTENCE_NONE, NULL, &create_opts); conn_opts.keepAliveInterval = 60; conn_opts.cleansession = 1; @@ -28,32 +81,37 @@ int MQTTinit(char **urls, char *client_id, char *topic, char *user, char *passwd if (urls[1]) conn_opts.serverURIs = urls; - if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) - return (rc); + if ((rc = MQTTAsync_connect(mqpriv->client, &conn_opts)) != MQTTASYNC_SUCCESS) { + fprintf(stderr, "MQTT: failed to connect\n"); + goto fail; + } if (topic == NULL) { - msgtopic = malloc(strlen(client_id) + strlen("acarsdec") + 2); + msgtopic = malloc(strlen(R.idstation) + strlen("acarsdec") + 2); if (msgtopic == NULL) - return -1; - sprintf(msgtopic, "acarsdec/%s", client_id); + goto fail; + sprintf(msgtopic, "acarsdec/%s", R.idstation); } else - msgtopic = topic; + msgtopic = strdup(topic); + mqpriv->msgtopic = msgtopic; + + return mqpriv; - return rc; +fail: + free(mqpriv); + return NULL; } -int MQTTsend(char *msgtxt) +void MQTTwrite(const void *buf, size_t buflen, mqttout_t *mqtt) { - pubmsg.payload = msgtxt; - pubmsg.payloadlen = strlen(msgtxt); - pubmsg.qos = 0; - pubmsg.retained = 0; - - return MQTTAsync_sendMessage(client, msgtopic, &pubmsg, NULL); + if (MQTTAsync_send(mqtt->client, mqtt->msgtopic, buflen, buf, 0, 0, NULL) != MQTTASYNC_SUCCESS) + fprintf(stderr, "failed to send MQTT\n"); } -void MQTTend() +void MQTTexit(mqttout_t *mqtt) { - MQTTAsync_disconnect(client, NULL); - MQTTAsync_destroy(&client); + MQTTAsync_disconnect(mqtt->client, NULL); + MQTTAsync_destroy(&mqtt->client); + free(mqtt->msgtopic); + free(mqtt); } diff --git a/mqttout.h b/mqttout.h new file mode 100644 index 0000000..b355851 --- /dev/null +++ b/mqttout.h @@ -0,0 +1,15 @@ +#ifndef mqttout_h +#define mqttout_h + +#include + +typedef struct { + MQTTAsync client; + char *msgtopic; +} mqttout_t; + +mqttout_t *MQTTinit(char *params); +void MQTTwrite(const void *buf, size_t buflen, mqttout_t *mqtt); +void MQTTexit(mqttout_t *mqtt); + +#endif /* mqttout_h */ diff --git a/netout.c b/netout.c index 60b6b93..fad7cc2 100644 --- a/netout.c +++ b/netout.c @@ -1,3 +1,23 @@ +/* + * Copyright (c) 2015 Thierry Leconte + * Copyright (c) 2024 Thibaut VARENE + * + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License version 2 + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + #include #include #include @@ -10,128 +30,82 @@ #include #include "acarsdec.h" +#include "netout.h" -static int sockfd = -1; -static struct sockaddr *netOutputAddr = NULL; -static int netOutputAddrLen = 0; - -int Netoutinit(char *Rawaddr) +// params is "host=xxx,port=yyy" +netout_t *Netoutinit(char *params) { - static char tmpAddr[256] = { 0 }; - char *addr; - char *port; + char *param, *sep; + char *addr = NULL; + char *port = NULL; struct addrinfo hints, *servinfo, *p; - int rv; + int sockfd, rv, ret = -1; + netout_t *netpriv = NULL; + + while ((param = strsep(¶ms, ","))) { + sep = strchr(param, '='); + if (!sep) + continue; + *sep++ = '\0'; + if (!strcmp("host", param)) + addr = sep; + else if (!strcmp("port", param)) + port = sep; + } - if (Rawaddr) - strncpy(tmpAddr, Rawaddr, 255); - else if (0 == tmpAddr[0]) - return -1; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; - addr = tmpAddr; - port = strstr(addr, ":"); if (port == NULL) port = "5555"; - else { - *port = 0; - port++; - } if (R.verbose) fprintf(stderr, "Attempting to resolve '%s:%s'.\n", addr, port); if ((rv = getaddrinfo(addr, port, &hints, &servinfo)) != 0) { fprintf(stderr, "Invalid/unknown error '%s' resolving '%s:%s', retrying later.\n", gai_strerror(rv), addr, port); - return -1; + return NULL; } for (p = servinfo; p != NULL; p = p->ai_next) { - if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) - continue; - - netOutputAddrLen = p->ai_addrlen; - netOutputAddr = malloc(netOutputAddrLen); - memcpy(netOutputAddr, p->ai_addr, netOutputAddrLen); - - freeaddrinfo(servinfo); - return 0; + sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (-1 != sockfd) + break; // success } - fprintf(stderr, "failed to resolve: '%s:%s'\n", addr, port); - - freeaddrinfo(servinfo); - - return -1; -} - -static void Netwrite(const void *buf, size_t count) -{ - int res; - - if (!netOutputAddrLen) { - /* The destination address hasn't yet been succesfully resolved. */ - if (R.verbose) - fprintf(stderr, "retrying DNS resolution.\n"); - - res = Netoutinit(NULL); - if (!res) /* Resolution failed, so we'll drop this message and try again next time. */ - return; + if (!p) { + fprintf(stderr, "failed to resolve: '%s:%s'\n", addr, port); + goto fail; } - res = sendto(sockfd, buf, count, 0, netOutputAddr, netOutputAddrLen); - if (R.verbose && res < 0) - fprintf(stderr, "error on sendto(): %s, ignoring.\n", strerror(errno)); -} - -void Netoutpp(acarsmsg_t *msg) -{ - static char pkt[3600]; // max. 16 blocks * 220 characters + extra space for msg prefix - char *pstr; - int res; - - if (!msg) - return; - - char *txt = strdup(msg->txt); - for (pstr = txt; *pstr != 0; pstr++) - if (*pstr == '\n' || *pstr == '\r') - *pstr = ' '; + netpriv = malloc(sizeof(*netpriv)); + if (!netpriv) + goto fail; - res = snprintf(pkt, sizeof(pkt), "AC%1c %7s %1c %2s %1c %4s %6s %s", - msg->mode, msg->addr, msg->ack, msg->label, msg->bid ? msg->bid : '.', msg->no, - msg->fid, txt); + memcpy(&netpriv->netOutputAddr, p->ai_addr, p->ai_addrlen); + netpriv->netOutputAddrLen = p->ai_addrlen; + netpriv->sockfd = sockfd; - free(txt); - Netwrite(pkt, res); +fail: + freeaddrinfo(servinfo); + return netpriv; } -void Netoutsv(acarsmsg_t *msg, char *idstation, int chn, struct timeval tv) +void Netwrite(const void *buf, size_t count, netout_t *net) { - static char pkt[3600]; // max. 16 blocks * 220 characters + extra space for msg prefix - struct tm tmp; int res; - if (!msg) + if (!net->netOutputAddrLen) return; - gmtime_r(&(tv.tv_sec), &tmp); - - res = snprintf(pkt, sizeof(pkt), - "%8s %1d %02d/%02d/%04d %02d:%02d:%02d %1d %03d %1c %7s %1c %2s %1c %4s %6s %s", - idstation, chn + 1, tmp.tm_mday, tmp.tm_mon + 1, - tmp.tm_year + 1900, tmp.tm_hour, tmp.tm_min, tmp.tm_sec, - msg->err, (int)(msg->lvl), msg->mode, msg->addr, msg->ack, msg->label, - msg->bid ? msg->bid : '.', msg->no, msg->fid, msg->txt); - - Netwrite(pkt, res); + res = sendto(net->sockfd, buf, count, 0, (struct sockaddr *)&net->netOutputAddr, net->netOutputAddrLen); + if (R.verbose && res < 0) + fprintf(stderr, "error on sendto(): %s, ignoring.\n", strerror(errno)); } -void Netoutjson(char *jsonbuf) +void Netexit(netout_t *net) { - if (jsonbuf) - Netwrite(jsonbuf, strlen(jsonbuf)); + free(net); } diff --git a/netout.h b/netout.h new file mode 100644 index 0000000..2067cd6 --- /dev/null +++ b/netout.h @@ -0,0 +1,18 @@ +#ifndef netout_h +#define netout_h + +#include +#include +#include + +typedef struct { + int sockfd; + struct sockaddr_storage netOutputAddr; + socklen_t netOutputAddrLen; +} netout_t; + +netout_t *Netoutinit(char *params); +void Netwrite(const void *buf, size_t count, netout_t *net); +void Netexit(netout_t *net); + +#endif /* netout_h */ diff --git a/output.c b/output.c index 42941f3..707ce54 100644 --- a/output.c +++ b/output.c @@ -1,3 +1,23 @@ +/* + * Copyright (c) 2015 Thierry Leconte + * Copyright (c) 2024 Thibaut VARENE + * + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License version 2 + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + #include #include #include @@ -19,13 +39,16 @@ #endif #include "acarsdec.h" #include "output.h" +#include "fileout.h" +#include "netout.h" +#ifdef WITH_MQTT +#include "mqttout.h" +#endif extern int label_filter(char *lbl); -static FILE *fdout; - -static char *jsonbuf = NULL; -#define JSONBUFLEN 30000 +#define FMTBUFLEN 30000 +static char fmtbuf[FMTBUFLEN+1]; #define IS_DOWNLINK_BLK(bid) ((bid) >= '0' && (bid) <= '9') @@ -104,144 +127,323 @@ static la_reasm_table_funcs acars_reasm_funcs = { #endif // HAVE_LIBACARS -static inline void cls(void) +static const struct { + const char *name; + int fmt; +} out_fmts[] = { + { "oneline", FMT_ONELINE, }, + { "full", FMT_FULL, }, + { "monitor", FMT_MONITOR, }, + { "pp", FMT_PP, }, + { "native", FMT_NATIVE, }, +#ifdef HAVE_CJSON + { "json", FMT_JSON, }, + { "routejson", FMT_ROUTEJSON, }, +#endif +}; + +static const struct { + const char *name; + int dst; +} out_dsts[] = { + { "file", DST_FILE, }, + { "udp", DST_UDP, }, +#ifdef WITH_MQTT + { "mqtt", DST_MQTT, }, +#endif +}; + +static bool validate_output(output_t *output) { - printf("\x1b[H\x1b[2J"); + if (!output->fmt || !output->dst) + return false; + + switch (output->dst) { + case DST_FILE: + // all formats valid + return true; + case DST_UDP: + // all but MONITOR valid + if (FMT_MONITOR == output->fmt) + return false; + else + return true; + case DST_MQTT: + // only JSON formats valid + switch (output->fmt) { + case FMT_JSON: + case FMT_ROUTEJSON: + return true; + default: + return false; + } + default: + return false; + } } -int initOutput(char *logfilename, char *Rawaddr) +// fmt:dst:param1=xxx,param2=yyy +int setup_output(char *outarg) { - if (R.outtype != OUTTYPE_NONE && logfilename) { - if ((fdout = Fileoutinit(logfilename)) == NULL) - return -1; - } else { - fdout = stdout; + // NB: outarg is taken from program global argv: it exists throughout the execution of the program + char **ap, *argv[3] = {0}; + output_t *output; + int i; + + // parse first 2 separators, leave the rest of the string untouched as argv[2] + for (ap = argv; ap < &argv[2] && (*ap = strsep(&outarg, ":")); ap++); + argv[2] = outarg; + + if (!argv[0] || '\0' == *argv[0] || !argv[1] || '\0' == *argv[1]) { + fprintf(stderr, "Not enough output arguments\n"); + return -1; // not enough arguments } - if (Rawaddr) - if (Netoutinit(Rawaddr)) - return -1; + output = calloc(1, sizeof(*output)); + if (!output) + return -1; // OOM - if (R.outtype == OUTTYPE_MONITOR) { - R.verbose = 0; - cls(); - fflush(stdout); + for (i = 0; i < ARRAY_SIZE(out_fmts); i++) { + if (!strcmp(argv[0], out_fmts[i].name)) { + output->fmt = out_fmts[i].fmt; + break; + } } -#ifdef HAVE_CJSON - if (R.outtype == OUTTYPE_JSON || R.outtype == OUTTYPE_ROUTEJSON || R.netout == NETLOG_JSON || R.netout == NETLOG_MQTT) { - jsonbuf = malloc(JSONBUFLEN + 1); - if (jsonbuf == NULL) - return -1; + + for (i = 0; i < ARRAY_SIZE(out_dsts); i++) { + if (!strcmp(argv[1], out_dsts[i].name)) { + output->dst = out_dsts[i].dst; + break; + } + } + + if (!validate_output(output)) { + fprintf(stderr, "Invalid output configuration: %s:%s\n", argv[0], argv[1]); + return -1; // invalid output config } + + if (argv[2] && '\0' != *argv[2]) + output->params = argv[2]; + + output->next = R.outputs; + R.outputs = output; + + return 0; + +fail: + free(output); + return -1; +} + +int initOutputs(void) +{ + output_t *out; + + if (!R.outputs) + return -1; + + for (out = R.outputs; out; out = out->next) { + switch (out->dst) { + case DST_FILE: + out->priv = Fileoutinit(out->params); + if (!out->priv) + return -1; + break; + case DST_UDP: + out->priv = Netoutinit(out->params); + if (!out->priv) + return -1; + break; +#ifdef WITH_MQTT + case DST_MQTT: + out->priv = MQTTinit(out->params); + if (!out->priv) + return -1; + break; #endif + default: + return -1; + } + + if (out->fmt == FMT_MONITOR) + R.verbose = 0; + } + #ifdef HAVE_LIBACARS reasm_ctx = R.skip_reassembly ? NULL : la_reasm_ctx_new(); #endif return 0; } -static void printtime(struct timeval tv) +void exitOutputs(void) +{ + output_t *out; + int ret; + + if (!R.outputs) + return; + + for (out = R.outputs; out; out = out->next) { + switch (out->dst) { + default: + case DST_FILE: + Fileoutexit(out->priv); + break; + case DST_UDP: + Netexit(out->priv); + break; +#ifdef WITH_MQTT + case DST_MQTT: + MQTTexit(out->priv); + break; +#endif + } + } +} + +static int fmt_sv(acarsmsg_t *msg, int chn, struct timeval tv, char *buf, size_t bufsz) +{ + struct tm tmp; + int res; + + if (!msg || !buf) + return -1; + + gmtime_r(&(tv.tv_sec), &tmp); + + return snprintf(buf, bufsz, + "%8s %1d %02d/%02d/%04d %02d:%02d:%02d %1d %03d %1c %7s %1c %2s %1c %4s %6s %s", + R.idstation, chn + 1, tmp.tm_mday, tmp.tm_mon + 1, + tmp.tm_year + 1900, tmp.tm_hour, tmp.tm_min, tmp.tm_sec, + msg->err, (int)(msg->lvl), msg->mode, msg->addr, msg->ack, msg->label, + msg->bid ? msg->bid : '.', msg->no, msg->fid, msg->txt); +} + +static int fmt_pp(acarsmsg_t *msg, char *buf, size_t bufsz) +{ + char *pstr; + int res; + + if (!msg || !buf) + return -1; + + char *txt = strdup(msg->txt); + for (pstr = txt; *pstr != 0; pstr++) + if (*pstr == '\n' || *pstr == '\r') + *pstr = ' '; + + res = snprintf(buf, bufsz, "AC%1c %7s %1c %2s %1c %4s %6s %s", + msg->mode, msg->addr, msg->ack, msg->label, msg->bid ? msg->bid : '.', msg->no, + msg->fid, txt); + + free(txt); + return res; +} + +static int fmt_time(struct timeval tv, char *buf, size_t bufsz) { struct tm tmp; gmtime_r(&(tv.tv_sec), &tmp); - fprintf(fdout, "%02d:%02d:%02d.%03ld", - tmp.tm_hour, tmp.tm_min, tmp.tm_sec, tv.tv_usec / 1000); + return snprintf(buf, bufsz, "%02d:%02d:%02d.%03ld", + tmp.tm_hour, tmp.tm_min, tmp.tm_sec, tv.tv_usec / 1000); } -static void printdate(struct timeval tv) +static int fmt_date(struct timeval tv, char *buf, size_t bufsz) { struct tm tmp; + int len; if (tv.tv_sec + tv.tv_usec == 0) - return; + return 0; gmtime_r(&(tv.tv_sec), &tmp); - fprintf(fdout, "%02d/%02d/%04d ", - tmp.tm_mday, tmp.tm_mon + 1, tmp.tm_year + 1900); - printtime(tv); + len = snprintf(buf, bufsz, "%02d/%02d/%04d ", + tmp.tm_mday, tmp.tm_mon + 1, tmp.tm_year + 1900); + return len + fmt_time(tv, buf + len, bufsz - len); } -static void printmsg(acarsmsg_t *msg, int chn, struct timeval tv) +static int fmt_msg(acarsmsg_t *msg, int chn, struct timeval tv, char *buf, size_t bufsz) { oooi_t oooi; + int len = 0; #if defined(WITH_RTL) || defined(WITH_AIR) || defined(WITH_SOAPY) if (R.inmode >= 3) - fprintf(fdout, "\n[#%1d (F:%3.3f L:%+5.1f E:%1d) ", chn + 1, + len += snprintf(buf + len, bufsz - len, "[#%1d (F:%3.3f L:%+5.1f E:%1d) ", chn + 1, R.channels[chn].Fr / 1000000.0, msg->lvl, msg->err); else #endif - fprintf(fdout, "\n[#%1d (L:%+5.1f E:%1d) ", chn + 1, msg->lvl, msg->err); + len += snprintf(buf + len, bufsz - len, "[#%1d (L:%+5.1f E:%1d) ", chn + 1, msg->lvl, msg->err); if (R.inmode != 2) - printdate(tv); + len += fmt_date(tv, buf + len, bufsz - len); - fprintf(fdout, " --------------------------------\n"); - fprintf(fdout, "Mode : %1c ", msg->mode); - fprintf(fdout, "Label : %2s ", msg->label); + len += snprintf(buf + len, bufsz - len, " --------------------------------\n"); + len += snprintf(buf + len, bufsz - len, "Mode : %1c ", msg->mode); + len += snprintf(buf + len, bufsz - len, "Label : %2s ", msg->label); if (msg->bid) { - fprintf(fdout, "Id : %1c ", msg->bid); + len += snprintf(buf + len, bufsz - len, "Id : %1c ", msg->bid); if (msg->ack == '!') - fprintf(fdout, "Nak\n"); + len += snprintf(buf + len, bufsz - len, "Nak\n"); else - fprintf(fdout, "Ack : %1c\n", msg->ack); - fprintf(fdout, "Aircraft reg: %s ", msg->addr); + len += snprintf(buf + len, bufsz - len, "Ack : %1c\n", msg->ack); + len += snprintf(buf + len, bufsz - len, "Aircraft reg: %s ", msg->addr); if (IS_DOWNLINK_BLK(msg->bid)) { - fprintf(fdout, "Flight id: %s\n", msg->fid); - fprintf(fdout, "No: %4s", msg->no); + len += snprintf(buf + len, bufsz - len, "Flight id: %s\n", msg->fid); + len += snprintf(buf + len, bufsz - len, "No: %4s", msg->no); } if (msg->sublabel[0] != '\0') { - fprintf(fdout, "\nSublabel: %s", msg->sublabel); + len += snprintf(buf + len, bufsz - len, "\nSublabel: %s", msg->sublabel); if (msg->mfi[0] != '\0') { - fprintf(fdout, " MFI: %s", msg->mfi); + len += snprintf(buf + len, bufsz - len, " MFI: %s", msg->mfi); } } #ifdef HAVE_LIBACARS if (!R.skip_reassembly) { - fprintf(fdout, "\nReassembly: %s", la_reasm_status_name_get(msg->reasm_status)); + len += snprintf(buf + len, bufsz - len, "\nReassembly: %s", la_reasm_status_name_get(msg->reasm_status)); } #endif } - fprintf(fdout, "\n"); + len += snprintf(buf + len, bufsz - len, "\n"); if (msg->txt[0]) - fprintf(fdout, "%s\n", msg->txt); + len += snprintf(buf + len, bufsz - len, "%s\n", msg->txt); if (msg->be == 0x17) - fprintf(fdout, "ETB\n"); + len += snprintf(buf + len, bufsz - len, "ETB\n"); if (DecodeLabel(msg, &oooi)) { - fprintf(fdout, "##########################\n"); + len += snprintf(buf + len, bufsz - len, "##########################\n"); if (oooi.da[0]) - fprintf(fdout, "Destination Airport : %s\n", oooi.da); + len += snprintf(buf + len, bufsz - len, "Destination Airport : %s\n", oooi.da); if (oooi.sa[0]) - fprintf(fdout, "Departure Airport : %s\n", oooi.sa); + len += snprintf(buf + len, bufsz - len, "Departure Airport : %s\n", oooi.sa); if (oooi.eta[0]) - fprintf(fdout, "Estimation Time of Arrival : %s\n", oooi.eta); + len += snprintf(buf + len, bufsz - len, "Estimation Time of Arrival : %s\n", oooi.eta); if (oooi.gout[0]) - fprintf(fdout, "Gate out Time : %s\n", oooi.gout); + len += snprintf(buf + len, bufsz - len, "Gate out Time : %s\n", oooi.gout); if (oooi.gin[0]) - fprintf(fdout, "Gate in Time : %s\n", oooi.gin); + len += snprintf(buf + len, bufsz - len, "Gate in Time : %s\n", oooi.gin); if (oooi.woff[0]) - fprintf(fdout, "Wheels off Tme : %s\n", oooi.woff); + len += snprintf(buf + len, bufsz - len, "Wheels off Tme : %s\n", oooi.woff); if (oooi.won[0]) - fprintf(fdout, "Wheels on Time : %s\n", oooi.won); + len += snprintf(buf + len, bufsz - len, "Wheels on Time : %s\n", oooi.won); } #ifdef HAVE_LIBACARS if (msg->decoded_tree != NULL) { la_vstring *vstr = la_proto_tree_format_text(NULL, msg->decoded_tree); - fprintf(fdout, "%s\n", vstr->str); + len += snprintf(buf + len, bufsz - len, "%s\n", vstr->str); la_vstring_destroy(vstr, true); } #endif - fflush(fdout); + return len; } #ifdef HAVE_CJSON -static int buildjson(acarsmsg_t *msg, int chn, struct timeval tv) +static int fmt_json(acarsmsg_t *msg, int chn, struct timeval tv, char *buf, size_t bufsz) { oooi_t oooi; #if defined(WITH_RTL) || defined(WITH_AIR) || defined(WITH_SOAPY) @@ -332,16 +534,17 @@ static int buildjson(acarsmsg_t *msg, int chn, struct timeval tv) cJSON_AddStringToObject(app_info, "ver", ACARSDEC_VERSION); } - ok = cJSON_PrintPreallocated(json_obj, jsonbuf, JSONBUFLEN, 0); + ok = cJSON_PrintPreallocated(json_obj, buf, bufsz, 0); cJSON_Delete(json_obj); - return ok; + return ok ? strlen(buf) : -1; } #endif /* HAVE_CJSON */ -static void printoneline(acarsmsg_t *msg, int chn, struct timeval tv) +static int fmt_oneline(acarsmsg_t *msg, int chn, struct timeval tv, char *buf, size_t bufsz) { char txt[60]; char *pstr; + int len; strncpy(txt, msg->txt, 59); txt[59] = 0; @@ -349,14 +552,14 @@ static void printoneline(acarsmsg_t *msg, int chn, struct timeval tv) if (*pstr == '\n' || *pstr == '\r') *pstr = ' '; - fprintf(fdout, "#%1d (L:%+5.1f E:%1d) ", chn + 1, msg->lvl, msg->err); + len = snprintf(buf, bufsz, "#%1d (L:%+5.1f E:%1d) ", chn + 1, msg->lvl, msg->err); if (R.inmode != 2) - printdate(tv); - fprintf(fdout, " %7s %6s %1c %2s %4s ", msg->addr, msg->fid, msg->mode, msg->label, msg->no); - fprintf(fdout, "%s", txt); - fprintf(fdout, "\n"); - fflush(fdout); + len += fmt_date(tv, buf + len, bufsz - len); + + len += snprintf(buf + len, bufsz - len, " %7s %6s %1c %2s %4s %s", msg->addr, msg->fid, msg->mode, msg->label, msg->no, txt); + + return len; } typedef struct flight_s flight_t; @@ -449,7 +652,7 @@ static flight_t *addFlight(acarsmsg_t *msg, int chn, struct timeval tv) } #ifdef HAVE_CJSON -static int routejson(flight_t *fl, struct timeval tv) +static int fmt_routejson(flight_t *fl, struct timeval tv, char *buf, size_t bufsz) { if (fl == NULL) return 0; @@ -470,64 +673,64 @@ static int routejson(flight_t *fl, struct timeval tv) cJSON_AddStringToObject(json_obj, "depa", fl->oooi.sa); cJSON_AddStringToObject(json_obj, "dsta", fl->oooi.da); - ok = cJSON_PrintPreallocated(json_obj, jsonbuf, JSONBUFLEN, 0); + ok = cJSON_PrintPreallocated(json_obj, buf, bufsz, 0); cJSON_Delete(json_obj); fl->rt = ok; - return ok; + return ok ? strlen(buf) : -1; } else return 0; } #endif /* HAVE_CJSON */ -static void printmonitor(acarsmsg_t *msg, int chn, struct timeval tv) +static int fmt_monitor(acarsmsg_t *msg, int chn, struct timeval tv, char *buf, size_t bufsz) { flight_t *fl; + int len = 0; - cls(); - - printf(" Acarsdec monitor "); - printtime(tv); - printf("\n Aircraft Flight Nb Channels First DEP ARR ETA\n"); + len += snprintf(buf + len, bufsz - len, "\x1b[H\x1b[2J"); + len += snprintf(buf + len, bufsz - len, " Acarsdec monitor "); + len += fmt_time(tv, buf + len, bufsz - len); + len += snprintf(buf + len, bufsz - len, "\n Aircraft Flight Nb Channels First DEP ARR ETA\n"); fl = flight_head; while (fl) { int i; - printf(" %-8s %-7s %3d ", fl->addr, fl->fid, fl->nbm); + len += snprintf(buf + len, bufsz - len, " %-8s %-7s %3d ", fl->addr, fl->fid, fl->nbm); for (i = 0; i < R.nbch; i++) - printf("%c", (fl->chm & (1 << i)) ? 'x' : '.'); + len += snprintf(buf + len, bufsz - len, "%c", (fl->chm & (1 << i)) ? 'x' : '.'); for (; i < R.nbch; i++) - printf(" "); - printf(" "); - printtime(fl->ts); + len += snprintf(buf + len, bufsz - len, " "); + len += snprintf(buf + len, bufsz - len, " "); + len += fmt_time(fl->ts, buf + len, bufsz - len); if (fl->oooi.sa[0]) - printf(" %4s ", fl->oooi.sa); + len += snprintf(buf + len, bufsz - len, " %4s ", fl->oooi.sa); else - printf(" "); + len += snprintf(buf + len, bufsz - len, " "); if (fl->oooi.da[0]) - printf(" %4s ", fl->oooi.da); + len += snprintf(buf + len, bufsz - len, " %4s ", fl->oooi.da); else - printf(" "); + len += snprintf(buf + len, bufsz - len, " "); if (fl->oooi.eta[0]) - printf(" %4s ", fl->oooi.eta); + len += snprintf(buf + len, bufsz - len, " %4s ", fl->oooi.eta); else - printf(" "); - printf("\n"); + len += snprintf(buf + len, bufsz - len, " "); + len += snprintf(buf + len, bufsz - len, "\n"); fl = fl->next; } - fflush(stdout); + return len; } void outputmsg(const msgblk_t *blk) { acarsmsg_t msg; int i, j, k; - int jok = 0; int outflg = 0; flight_t *fl; + output_t *out; /* fill msg struct */ memset(&msg, 0, sizeof(msg)); @@ -686,62 +889,55 @@ void outputmsg(const msgblk_t *blk) if (R.emptymsg && (msg.txt == NULL || msg.txt[0] == '\0')) return; + // for now and until we see contention, we don't bother with using a separate thread for outputs + for (out = R.outputs; out; out = out->next) { + int len = 0; + switch (out->fmt) { + case FMT_MONITOR: + len = fmt_monitor(&msg, blk->chn, blk->tv, fmtbuf, FMTBUFLEN); + break; + case FMT_ONELINE: + len = fmt_oneline(&msg, blk->chn, blk->tv, fmtbuf, FMTBUFLEN); + break; + case FMT_FULL: + len = fmt_msg(&msg, blk->chn, blk->tv, fmtbuf, FMTBUFLEN); + break; + case FMT_NATIVE: + len = fmt_sv(&msg, blk->chn, blk->tv, fmtbuf, FMTBUFLEN); + break; + case FMT_PP: + len = fmt_pp(&msg, fmtbuf, FMTBUFLEN); + break; #ifdef HAVE_CJSON - if (jsonbuf) { - if (R.outtype == OUTTYPE_ROUTEJSON) { - if (fl) - jok = routejson(fl, blk->tv); - } else { - jok = buildjson(&msg, blk->chn, blk->tv); - } - } + case FMT_ROUTEJSON: + len = fl ? fmt_routejson(fl, blk->tv, fmtbuf, FMTBUFLEN) : -1; + break;; + case FMT_JSON: + len = fmt_json(&msg, blk->chn, blk->tv, fmtbuf, FMTBUFLEN); + break; #endif /* HAVE_CJSON */ - - if ((R.hourly || R.daily) && R.outtype != OUTTYPE_NONE && (fdout = Fileoutrotate(fdout)) == NULL) - _exit(1); - - switch (R.outtype) { - case OUTTYPE_NONE: - break; - case OUTTYPE_ONELINE: - printoneline(&msg, blk->chn, blk->tv); - break; - case OUTTYPE_STD: - printmsg(&msg, blk->chn, blk->tv); - break; - case OUTTYPE_MONITOR: - printmonitor(&msg, blk->chn, blk->tv); - break; -#ifdef HAVE_CJSON - case OUTTYPE_ROUTEJSON: - case OUTTYPE_JSON: - if (jok) { - fprintf(fdout, "%s\n", jsonbuf); - fflush(fdout); } - break; -#endif /* HAVE_CJSON */ - } - switch (R.netout) { - case NETLOG_PLANEPLOTTER: - Netoutpp(&msg); - break; - case NETLOG_NATIVE: - Netoutsv(&msg, R.idstation, blk->chn, blk->tv); - break; -#ifdef HAVE_CJSON - case NETLOG_JSON: - if (jok) - Netoutjson(jsonbuf); - break; + // NB if the same format is used for multiple outputs, the buffer will be recomputed each time. Deemed acceptable + + if (len <= 0) + continue; + + switch (out->dst) { + case DST_FILE: + Filewrite(fmtbuf, len, out->priv); + break; + case DST_UDP: + Netwrite(fmtbuf, len, out->priv); + break; #ifdef WITH_MQTT - case NETLOG_MQTT: - MQTTsend(jsonbuf); - break; -#endif /* WITH_MQTT */ -#endif /* HAVE_CJSON */ + case DST_MQTT: + MQTTwrite(fmtbuf, len, out->priv); + break; +#endif + } } + free(msg.txt); #ifdef HAVE_LIBACARS la_proto_tree_destroy(msg.decoded_tree); diff --git a/output.h b/output.h index 2293b2d..de9aca1 100644 --- a/output.h +++ b/output.h @@ -1,7 +1,11 @@ -extern int Netoutinit(char *Rawaddr); -extern void Netoutpp(acarsmsg_t *msg); -extern void Netoutsv(acarsmsg_t *msg, char *idstation, int chn, struct timeval tv); -extern void Netoutjson(char *jsonbuf); +#ifndef output_h +#define output_h -extern FILE *Fileoutinit(char *logfilename); -extern FILE *Fileoutrotate(FILE *fd); +#include "acarsdec.h" + +int setup_output(char *outarg); +int initOutputs(void); +void exitOutputs(void); +void outputmsg(const msgblk_t *blk); + +#endif /* output_h */