dump1090/net_io.c
Oliver Jowett 4e177c2d64 Store computed reception time in the message struct so we don't rely on
the message being emitted immediately.

Fix computation of reception time so it's more sensible (the block timestamp
is some time after reception of the _end_ of the block, not the start) - this
means that message-emission times are always later than message-reception
times in SBS output, which is a bit more sensible.

Use clock_gettime in preference to ftime.
2015-02-08 17:46:01 +00:00

1620 lines
53 KiB
C

// Part of dump1090, a Mode S message decoder for RTLSDR devices.
//
// net_io.c: network handling.
//
// Copyright (c) 2014,2015 Oliver Jowett <oliver@mutability.co.uk>
//
// This file is free software: you may copy, redistribute and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 2 of the License, or (at your
// option) any later version.
//
// This file 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
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// This file incorporates work covered by the following copyright and
// permission notice:
//
// Copyright (C) 2012 by Salvatore Sanfilippo <antirez@gmail.com>
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "dump1090.h"
#include <assert.h>
//
// ============================= Networking =============================
//
// Note: here we disregard any kind of good coding practice in favor of
// extreme simplicity, that is:
//
// 1) We only rely on the kernel buffers for our I/O without any kind of
// user space buffering.
// 2) We don't register any kind of event handler, from time to time a
// function gets called and we accept new connections. All the rest is
// handled via non-blocking I/O and manually polling clients to see if
// they have something new to share with us when reading is needed.
//
//=========================================================================
//
// Networking "stack" initialization
//
struct service {
char *descr;
int *socket;
struct net_writer *writer;
int port;
int enabled;
};
struct service services[MODES_NET_SERVICES_NUM];
void modesInitNet(void) {
int j;
struct service svc[MODES_NET_SERVICES_NUM] = {
{"Raw TCP output", &Modes.raw_out.socket, &Modes.raw_out, Modes.net_output_raw_port, 1},
{"Raw TCP input", &Modes.ris, NULL, Modes.net_input_raw_port, 1},
{"Beast TCP output", &Modes.beast_out.socket, &Modes.beast_out, Modes.net_output_beast_port, 1},
{"Beast TCP input", &Modes.bis, NULL, Modes.net_input_beast_port, 1},
{"HTTP server", &Modes.https, NULL, Modes.net_http_port, 1},
{"Basestation TCP output", &Modes.sbs_out.socket, &Modes.sbs_out, Modes.net_output_sbs_port, 1},
{"FlightAware TSV output", &Modes.fatsv_out.socket, &Modes.fatsv_out, Modes.net_fatsv_port, 1}
};
memcpy(&services, &svc, sizeof(svc));//services = svc;
Modes.clients = NULL;
#ifdef _WIN32
if ( (!Modes.wsaData.wVersion)
&& (!Modes.wsaData.wHighVersion) ) {
// Try to start the windows socket support
if (WSAStartup(MAKEWORD(2,1),&Modes.wsaData) != 0)
{
fprintf(stderr, "WSAStartup returned Error\n");
}
}
#endif
for (j = 0; j < MODES_NET_SERVICES_NUM; j++) {
services[j].enabled = (services[j].port != 0);
if (services[j].enabled) {
int s = anetTcpServer(Modes.aneterr, services[j].port, Modes.net_bind_address);
if (s == -1) {
fprintf(stderr, "Error opening the listening port %d (%s): %s\n",
services[j].port, services[j].descr, Modes.aneterr);
exit(1);
}
anetNonBlock(Modes.aneterr, s);
*services[j].socket = s;
if (services[j].writer) {
if (! (services[j].writer->data = malloc(MODES_OUT_BUF_SIZE)) ) {
fprintf(stderr, "Out of memory allocating output buffer for service %s\n", services[j].descr);
exit(1);
}
services[j].writer->socket = s;
services[j].writer->connections = 0;
services[j].writer->dataUsed = 0;
services[j].writer->lastWrite = time(NULL);
}
} else {
if (Modes.debug & MODES_DEBUG_NET) printf("%s port is disabled\n", services[j].descr);
}
}
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN);
#endif
}
//
//=========================================================================
//
// This function gets called from time to time when the decoding thread is
// awakened by new data arriving. This usually happens a few times every second
//
struct client * modesAcceptClients(void) {
int fd, port;
unsigned int j;
struct client *c;
for (j = 0; j < MODES_NET_SERVICES_NUM; j++) {
if (services[j].enabled) {
fd = anetTcpAccept(Modes.aneterr, *services[j].socket, NULL, &port);
if (fd == -1) continue;
anetNonBlock(Modes.aneterr, fd);
c = (struct client *) malloc(sizeof(*c));
c->service = *services[j].socket;
c->next = Modes.clients;
c->fd = fd;
c->buflen = 0;
Modes.clients = c;
anetSetSendBuffer(Modes.aneterr,fd, (MODES_NET_SNDBUF_SIZE << Modes.net_sndbuf_size));
if (services[j].writer) {
if (++ services[j].writer->connections == 1) {
services[j].writer->lastWrite = time(NULL); // suppress heartbeat initially
}
}
j--; // Try again with the same listening port
if (Modes.debug & MODES_DEBUG_NET)
printf("Created new client %d\n", fd);
}
}
return Modes.clients;
}
//
//=========================================================================
//
// On error free the client, collect the structure, adjust maxfd if needed.
//
void modesCloseClient(struct client *c) {
int j;
// Clean up, but defer removing from the list until modesNetCleanup().
// This is because there may be stackframes still pointing at this
// client (unpredictably: reading from client A may cause client B to
// be freed)
close(c->fd);
for (j = 0; j < MODES_NET_SERVICES_NUM; j++) {
if (c->service == *services[j].socket) {
if (services[j].writer)
services[j].writer->connections--;
break;
}
}
if (Modes.debug & MODES_DEBUG_NET)
printf("Closing client %d\n", c->fd);
// mark it as inactive and ready to be freed
c->fd = -1;
c->service = -1;
}
//
//=========================================================================
//
// Send the write buffer for the specified writer to all connected clients
//
static void flushWrites(struct net_writer *writer) {
struct client *c;
for (c = Modes.clients; c; c = c->next) {
if (c->service == writer->socket) {
#ifndef _WIN32
int nwritten = write(c->fd, writer->data, writer->dataUsed);
#else
int nwritten = send(c->fd, writer->data, writer->dataUsed, 0 );
#endif
if (nwritten != writer->dataUsed) {
modesCloseClient(c);
}
}
}
writer->dataUsed = 0;
writer->lastWrite = time(NULL);
}
// Prepare to write up to 'len' bytes to the given net_writer.
// Returns a pointer to write to, or NULL to skip this write.
static void *prepareWrite(struct net_writer *writer, int len) {
if (!writer ||
!writer->connections ||
!writer->data)
return NULL;
if (len > MODES_OUT_BUF_SIZE)
return NULL;
if (writer->dataUsed + len >= MODES_OUT_BUF_SIZE) {
// Flush now to free some space
flushWrites(writer);
}
return writer->data + writer->dataUsed;
}
// Complete a write previously begun by prepareWrite.
// endptr should point one byte past the last byte written
// to the buffer returned from prepareWrite.
static void completeWrite(struct net_writer *writer, void *endptr) {
writer->dataUsed = endptr - writer->data;
if (writer->dataUsed >= Modes.net_output_flush_size) {
flushWrites(writer);
}
}
//
//=========================================================================
//
// Write raw output in Beast Binary format with Timestamp to TCP clients
//
void modesSendBeastOutput(struct modesMessage *mm) {
int msgLen = mm->msgbits / 8;
char *p = prepareWrite(&Modes.beast_out, 2 + 2 * (7 + msgLen));
char * pTimeStamp;
char ch;
int j;
unsigned char *msg = (Modes.net_verbatim ? mm->verbatim : mm->msg);
if (!p)
return;
*p++ = 0x1a;
if (msgLen == MODES_SHORT_MSG_BYTES)
{*p++ = '2';}
else if (msgLen == MODES_LONG_MSG_BYTES)
{*p++ = '3';}
else if (msgLen == MODEAC_MSG_BYTES)
{*p++ = '1';}
else
{return;}
pTimeStamp = (char *) &mm->timestampMsg;
for (j = 5; j >= 0; j--) {
*p++ = (ch = pTimeStamp[j]);
if (0x1A == ch) {*p++ = ch; }
}
*p++ = ch = (char) round(sqrt(mm->signalLevel) * 255);
if (0x1A == ch) {*p++ = ch; }
for (j = 0; j < msgLen; j++) {
*p++ = (ch = msg[j]);
if (0x1A == ch) {*p++ = ch; }
}
completeWrite(&Modes.beast_out, p);
}
//
//=========================================================================
//
// Write raw output to TCP clients
//
void modesSendRawOutput(struct modesMessage *mm) {
int msgLen = mm->msgbits / 8;
char *p = prepareWrite(&Modes.raw_out, msgLen*2 + 15);
int j;
unsigned char * pTimeStamp;
unsigned char *msg = (Modes.net_verbatim ? mm->verbatim : mm->msg);
if (!p)
return;
if (Modes.mlat && mm->timestampMsg) {
*p++ = '@';
pTimeStamp = (unsigned char *) &mm->timestampMsg;
for (j = 5; j >= 0; j--) {
sprintf(p, "%02X", pTimeStamp[j]);
p += 2;
}
} else
*p++ = '*';
for (j = 0; j < msgLen; j++) {
sprintf(p, "%02X", msg[j]);
p += 2;
}
*p++ = ';';
*p++ = '\n';
completeWrite(&Modes.raw_out, p);
}
//
//=========================================================================
//
// Write SBS output to TCP clients
// The message structure mm->bFlags tells us what has been updated by this message
//
void modesSendSBSOutput(struct modesMessage *mm) {
char *p;
struct timespec now;
struct tm stTime_receive, stTime_now;
int msgType;
// For now, suppress non-ICAO addresses
if (mm->addr & MODES_NON_ICAO_ADDRESS)
return;
p = prepareWrite(&Modes.sbs_out, 200);
if (!p)
return;
//
// SBS BS style output checked against the following reference
// http://www.homepages.mcb.net/bones/SBS/Article/Barebones42_Socket_Data.htm - seems comprehensive
//
if (mm->msgtype == -1) {
// heartbeat
p += sprintf(p, "\r\n");
completeWrite(&Modes.sbs_out, p);
return;
}
// Decide on the basic SBS Message Type
if ((mm->msgtype == 4) || (mm->msgtype == 20)) {
msgType = 5;
} else if ((mm->msgtype == 5) || (mm->msgtype == 21)) {
msgType = 6;
} else if ((mm->msgtype == 0) || (mm->msgtype == 16)) {
msgType = 7;
} else if (mm->msgtype == 11) {
msgType = 8;
} else if ((mm->msgtype != 17) && (mm->msgtype != 18)) {
return;
} else if ((mm->metype >= 1) && (mm->metype <= 4)) {
msgType = 1;
} else if ((mm->metype >= 5) && (mm->metype <= 8)) {
if (mm->bFlags & MODES_ACFLAGS_LATLON_VALID)
{msgType = 2;}
else
{msgType = 7;}
} else if ((mm->metype >= 9) && (mm->metype <= 18)) {
if (mm->bFlags & MODES_ACFLAGS_LATLON_VALID)
{msgType = 3;}
else
{msgType = 7;}
} else if (mm->metype != 19) {
return;
} else if ((mm->mesub == 1) || (mm->mesub == 2)) {
msgType = 4;
} else {
return;
}
// Fields 1 to 6 : SBS message type and ICAO address of the aircraft and some other stuff
p += sprintf(p, "MSG,%d,111,11111,%06X,111111,", msgType, mm->addr);
// Find current system time
clock_gettime(CLOCK_REALTIME, &now);
localtime_r(&now.tv_sec, &stTime_now);
// Find message reception time
localtime_r(&mm->sysTimestampMsg.tv_sec, &stTime_receive);
// Fields 7 & 8 are the message reception time and date
p += sprintf(p, "%04d/%02d/%02d,", (stTime_receive.tm_year+1900),(stTime_receive.tm_mon+1), stTime_receive.tm_mday);
p += sprintf(p, "%02d:%02d:%02d.%03u,", stTime_receive.tm_hour, stTime_receive.tm_min, stTime_receive.tm_sec, (unsigned) (mm->sysTimestampMsg.tv_nsec / 1000000U));
// Fields 9 & 10 are the current time and date
p += sprintf(p, "%04d/%02d/%02d,", (stTime_now.tm_year+1900),(stTime_now.tm_mon+1), stTime_now.tm_mday);
p += sprintf(p, "%02d:%02d:%02d.%03u", stTime_now.tm_hour, stTime_now.tm_min, stTime_now.tm_sec, (unsigned) (now.tv_nsec / 1000000U));
// Field 11 is the callsign (if we have it)
if (mm->bFlags & MODES_ACFLAGS_CALLSIGN_VALID) {p += sprintf(p, ",%s", mm->flight);}
else {p += sprintf(p, ",");}
// Field 12 is the altitude (if we have it) - force to zero if we're on the ground
if ((mm->bFlags & MODES_ACFLAGS_AOG_GROUND) == MODES_ACFLAGS_AOG_GROUND) {
p += sprintf(p, ",0");
} else if (mm->bFlags & MODES_ACFLAGS_ALTITUDE_VALID) {
p += sprintf(p, ",%d", mm->altitude);
} else {
p += sprintf(p, ",");
}
// Field 13 is the ground Speed (if we have it)
if (mm->bFlags & MODES_ACFLAGS_SPEED_VALID) {
p += sprintf(p, ",%d", mm->velocity);
} else {
p += sprintf(p, ",");
}
// Field 14 is the ground Heading (if we have it)
if (mm->bFlags & MODES_ACFLAGS_HEADING_VALID) {
p += sprintf(p, ",%d", mm->heading);
} else {
p += sprintf(p, ",");
}
// Fields 15 and 16 are the Lat/Lon (if we have it)
if (mm->bFlags & MODES_ACFLAGS_LATLON_VALID) {p += sprintf(p, ",%1.5f,%1.5f", mm->fLat, mm->fLon);}
else {p += sprintf(p, ",,");}
// Field 17 is the VerticalRate (if we have it)
if (mm->bFlags & MODES_ACFLAGS_VERTRATE_VALID) {p += sprintf(p, ",%d", mm->vert_rate);}
else {p += sprintf(p, ",");}
// Field 18 is the Squawk (if we have it)
if (mm->bFlags & MODES_ACFLAGS_SQUAWK_VALID) {p += sprintf(p, ",%x", mm->modeA);}
else {p += sprintf(p, ",");}
// Field 19 is the Squawk Changing Alert flag (if we have it)
if (mm->bFlags & MODES_ACFLAGS_FS_VALID) {
if ((mm->fs >= 2) && (mm->fs <= 4)) {
p += sprintf(p, ",-1");
} else {
p += sprintf(p, ",0");
}
} else {
p += sprintf(p, ",");
}
// Field 20 is the Squawk Emergency flag (if we have it)
if (mm->bFlags & MODES_ACFLAGS_SQUAWK_VALID) {
if ((mm->modeA == 0x7500) || (mm->modeA == 0x7600) || (mm->modeA == 0x7700)) {
p += sprintf(p, ",-1");
} else {
p += sprintf(p, ",0");
}
} else {
p += sprintf(p, ",");
}
// Field 21 is the Squawk Ident flag (if we have it)
if (mm->bFlags & MODES_ACFLAGS_FS_VALID) {
if ((mm->fs >= 4) && (mm->fs <= 5)) {
p += sprintf(p, ",-1");
} else {
p += sprintf(p, ",0");
}
} else {
p += sprintf(p, ",");
}
// Field 22 is the OnTheGround flag (if we have it)
if (mm->bFlags & MODES_ACFLAGS_AOG_VALID) {
if (mm->bFlags & MODES_ACFLAGS_AOG) {
p += sprintf(p, ",-1");
} else {
p += sprintf(p, ",0");
}
} else {
p += sprintf(p, ",");
}
p += sprintf(p, "\r\n");
completeWrite(&Modes.sbs_out, p);
}
//
//=========================================================================
//
void modesQueueOutput(struct modesMessage *mm) {
modesSendSBSOutput(mm);
modesSendBeastOutput(mm);
modesSendRawOutput(mm);
}
//
//=========================================================================
//
// This function decodes a Beast binary format message
//
// The message is passed to the higher level layers, so it feeds
// the selected screen output, the network output and so forth.
//
// If the message looks invalid it is silently discarded.
//
// The function always returns 0 (success) to the caller as there is no
// case where we want broken messages here to close the client connection.
//
int decodeBinMessage(struct client *c, char *p) {
int msgLen = 0;
int j;
char ch;
char * ptr;
unsigned char msg[MODES_LONG_MSG_BYTES];
struct modesMessage mm;
MODES_NOTUSED(c);
memset(&mm, 0, sizeof(mm));
ch = *p++; /// Get the message type
if (0x1A == ch) {p++;}
if ((ch == '1') && (Modes.mode_ac)) { // skip ModeA/C unless user enables --modes-ac
msgLen = MODEAC_MSG_BYTES;
} else if (ch == '2') {
msgLen = MODES_SHORT_MSG_BYTES;
} else if (ch == '3') {
msgLen = MODES_LONG_MSG_BYTES;
}
if (msgLen) {
// Mark messages received over the internet as remote so that we don't try to
// pass them off as being received by this instance when forwarding them
mm.remote = 1;
ptr = (char*) &mm.timestampMsg;
for (j = 0; j < 6; j++) { // Grab the timestamp (big endian format)
ptr[5-j] = ch = *p++;
if (0x1A == ch) {p++;}
}
// record reception time as the time we read it.
clock_gettime(CLOCK_REALTIME, &mm.sysTimestampMsg);
ch = *p++; // Grab the signal level
mm.signalLevel = ((unsigned char)ch / 256.0);
mm.signalLevel = mm.signalLevel * mm.signalLevel + 1e-5;
if (0x1A == ch) {p++;}
for (j = 0; j < msgLen; j++) { // and the data
msg[j] = ch = *p++;
if (0x1A == ch) {p++;}
}
if (msgLen == MODEAC_MSG_BYTES) { // ModeA or ModeC
Modes.stats_current.remote_received_modeac++;
decodeModeAMessage(&mm, ((msg[0] << 8) | msg[1]));
} else {
int result;
Modes.stats_current.remote_received_modes++;
result = decodeModesMessage(&mm, msg);
if (result < 0) {
if (result == -1)
Modes.stats_current.remote_rejected_unknown_icao++;
else
Modes.stats_current.remote_rejected_bad++;
return 0;
} else {
Modes.stats_current.remote_accepted[mm.correctedbits]++;
}
}
useModesMessage(&mm);
}
return (0);
}
//
//=========================================================================
//
// Turn an hex digit into its 4 bit decimal value.
// Returns -1 if the digit is not in the 0-F range.
//
int hexDigitVal(int c) {
c = tolower(c);
if (c >= '0' && c <= '9') return c-'0';
else if (c >= 'a' && c <= 'f') return c-'a'+10;
else return -1;
}
//
//=========================================================================
//
// This function decodes a string representing message in raw hex format
// like: *8D4B969699155600E87406F5B69F; The string is null-terminated.
//
// The message is passed to the higher level layers, so it feeds
// the selected screen output, the network output and so forth.
//
// If the message looks invalid it is silently discarded.
//
// The function always returns 0 (success) to the caller as there is no
// case where we want broken messages here to close the client connection.
//
int decodeHexMessage(struct client *c, char *hex) {
int l = strlen(hex), j;
unsigned char msg[MODES_LONG_MSG_BYTES];
struct modesMessage mm;
MODES_NOTUSED(c);
memset(&mm, 0, sizeof(mm));
// Mark messages received over the internet as remote so that we don't try to
// pass them off as being received by this instance when forwarding them
mm.remote = 1;
mm.signalLevel = 1e-5;
// Remove spaces on the left and on the right
while(l && isspace(hex[l-1])) {
hex[l-1] = '\0'; l--;
}
while(isspace(*hex)) {
hex++; l--;
}
// Turn the message into binary.
// Accept *-AVR raw @-AVR/BEAST timeS+raw %-AVR timeS+raw (CRC good) <-BEAST timeS+sigL+raw
// and some AVR records that we can understand
if (hex[l-1] != ';') {return (0);} // not complete - abort
switch(hex[0]) {
case '<': {
mm.signalLevel = ((hexDigitVal(hex[13])<<4) | hexDigitVal(hex[14])) / 256.0;
mm.signalLevel = mm.signalLevel * mm.signalLevel + 1e-5;
hex += 15; l -= 16; // Skip <, timestamp and siglevel, and ;
break;}
case '@': // No CRC check
case '%': { // CRC is OK
hex += 13; l -= 14; // Skip @,%, and timestamp, and ;
break;}
case '*':
case ':': {
hex++; l-=2; // Skip * and ;
break;}
default: {
return (0); // We don't know what this is, so abort
break;}
}
if ( (l != (MODEAC_MSG_BYTES * 2))
&& (l != (MODES_SHORT_MSG_BYTES * 2))
&& (l != (MODES_LONG_MSG_BYTES * 2)) )
{return (0);} // Too short or long message... broken
if ( (0 == Modes.mode_ac)
&& (l == (MODEAC_MSG_BYTES * 2)) )
{return (0);} // Right length for ModeA/C, but not enabled
for (j = 0; j < l; j += 2) {
int high = hexDigitVal(hex[j]);
int low = hexDigitVal(hex[j+1]);
if (high == -1 || low == -1) return 0;
msg[j/2] = (high << 4) | low;
}
// record reception time as the time we read it.
clock_gettime(CLOCK_REALTIME, &mm.sysTimestampMsg);
if (l == (MODEAC_MSG_BYTES * 2)) { // ModeA or ModeC
Modes.stats_current.remote_received_modeac++;
decodeModeAMessage(&mm, ((msg[0] << 8) | msg[1]));
} else { // Assume ModeS
int result;
Modes.stats_current.remote_received_modes++;
result = decodeModesMessage(&mm, msg);
if (result < 0) {
if (result == -1)
Modes.stats_current.remote_rejected_unknown_icao++;
else
Modes.stats_current.remote_rejected_bad++;
return 0;
} else {
Modes.stats_current.remote_accepted[mm.correctedbits]++;
}
}
useModesMessage(&mm);
return (0);
}
//
//=========================================================================
//
// Return a description of planes in json. No metric conversion
//
// usual caveats about function-returning-pointer-to-static-buffer apply
static const char *jsonEscapeString(const char *str) {
static char buf[1024];
const char *in = str;
char *out = buf, *end = buf + sizeof(buf) - 10;
for (; *in && out < end; ++in) {
unsigned char ch = *in;
if (ch == '"' || ch == '\\') {
*out++ = '\\';
*out++ = ch;
} else if (ch < 32 || ch > 127) {
out += snprintf(out, end - out, "\\u%04x", ch);
} else {
*out++ = ch;
}
}
*out++ = 0;
return buf;
}
char *generateAircraftJson(const char *url_path, int *len) {
time_t now = time(NULL);
struct aircraft *a;
int buflen = 1024; // The initial buffer is incremented as needed
char *buf = (char *) malloc(buflen), *p = buf, *end = buf+buflen;
int first = 1;
MODES_NOTUSED(url_path);
p += snprintf(p, end-p,
"{ \"now\" : %d,\n"
" \"messages\" : %u,\n"
" \"aircraft\" : [",
(int)now,
Modes.stats_current.messages_total + Modes.stats_alltime.messages_total);
for (a = Modes.aircrafts; a; a = a->next) {
if (a->modeACflags & MODEAC_MSG_FLAG) { // skip any fudged ICAO records Mode A/C
continue;
}
if (a->messages < 2) { // basic filter for bad decodes
continue;
}
if (first)
first = 0;
else
*p++ = ',';
p += snprintf(p, end-p, "\n {\"hex\":\"%s%06x\"", (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF);
if (a->bFlags & MODES_ACFLAGS_SQUAWK_VALID)
p += snprintf(p, end-p, ",\"squawk\":\"%04x\"", a->modeA);
if (a->bFlags & MODES_ACFLAGS_CALLSIGN_VALID)
p += snprintf(p, end-p, ",\"flight\":\"%s\"", jsonEscapeString(a->flight));
if (a->bFlags & MODES_ACFLAGS_LATLON_VALID)
p += snprintf(p, end-p, ",\"lat\":%f,\"lon\":%f,\"seen_pos\":%d", a->lat, a->lon, (int)(now - a->seenLatLon));
if ((a->bFlags & MODES_ACFLAGS_AOG_VALID) && (a->bFlags & MODES_ACFLAGS_AOG))
p += snprintf(p, end-p, ",\"altitude\":\"ground\"");
else if (a->bFlags & MODES_ACFLAGS_ALTITUDE_VALID)
p += snprintf(p, end-p, ",\"altitude\":%d", a->altitude);
if (a->bFlags & MODES_ACFLAGS_VERTRATE_VALID)
p += snprintf(p, end-p, ",\"vert_rate\":%d", a->vert_rate);
if (a->bFlags & MODES_ACFLAGS_HEADING_VALID)
p += snprintf(p, end-p, ",\"track\":%d", a->track);
if (a->bFlags & MODES_ACFLAGS_SPEED_VALID)
p += snprintf(p, end-p, ",\"speed\":%d", a->speed);
p += snprintf(p, end-p, ",\"messages\":%ld,\"seen\":%d,\"rssi\":%.1f}",
a->messages, (int)(now - a->seen),
10 * log10((a->signalLevel[0] + a->signalLevel[1] + a->signalLevel[2] + a->signalLevel[3] +
a->signalLevel[4] + a->signalLevel[5] + a->signalLevel[6] + a->signalLevel[7] + 1e-5) / 8));
// If we're getting near the end of the buffer, expand it.
if ((end - p) < 256) {
int used = p - buf;
buflen *= 2;
buf = (char *) realloc(buf, buflen);
p = buf+used;
end = buf + buflen;
}
}
p += snprintf(p, end-p, "\n ]\n}\n");
*len = p-buf;
return buf;
}
static char * appendStatsJson(char *p,
char *end,
struct stats *st,
const char *key)
{
int i;
p += snprintf(p, end-p,
"\"%s\":{\"start\":%d,\"end\":%d",
key,
(int)st->start,
(int)st->end);
if (!Modes.net_only) {
p += snprintf(p, end-p,
",\"local\":{\"blocks_processed\":%u"
",\"blocks_dropped\":%u"
",\"modeac\":%u"
",\"modes\":%u"
",\"bad\":%u"
",\"unknown_icao\":%u",
st->blocks_processed,
st->blocks_dropped,
st->demod_modeac,
st->demod_preambles,
st->demod_rejected_bad,
st->demod_rejected_unknown_icao);
for (i=0; i <= Modes.nfix_crc; ++i) {
if (i == 0) p += snprintf(p, end-p, ",\"accepted\":[%u", st->demod_accepted[i]);
else p += snprintf(p, end-p, ",%u", st->demod_accepted[i]);
}
p += snprintf(p, end-p, "]");
if (st->signal_power_count > 0)
p += snprintf(p, end-p,",\"signal\":%.1f", 10 * log10(st->signal_power_sum / st->signal_power_count));
if (st->noise_power_count > 0)
p += snprintf(p, end-p,",\"noise\":%.1f", 10 * log10(st->noise_power_sum / st->noise_power_count));
if (st->peak_signal_power > 0)
p += snprintf(p, end-p,",\"peak_signal\":%.1f", 10 * log10(st->peak_signal_power));
p += snprintf(p, end-p,",\"strong_signals\":%d}", st->strong_signal_count);
}
if (Modes.net) {
p += snprintf(p, end-p,
",\"remote\":{\"modeac\":%u"
",\"modes\":%u"
",\"bad\":%u"
",\"unknown_icao\":%u",
st->remote_received_modeac,
st->remote_received_modes,
st->remote_rejected_bad,
st->remote_rejected_unknown_icao);
for (i=0; i <= Modes.nfix_crc; ++i) {
if (i == 0) p += snprintf(p, end-p, ",\"accepted\":[%u", st->remote_accepted[i]);
else p += snprintf(p, end-p, ",%u", st->remote_accepted[i]);
}
p += snprintf(p, end-p, "]");
p += snprintf(p, end-p, "},\"http_requests\":%u", st->http_requests);
}
{
uint64_t demod_cpu_millis = (uint64_t)st->demod_cpu.tv_sec*1000UL + st->demod_cpu.tv_nsec/1000000UL;
uint64_t reader_cpu_millis = (uint64_t)st->reader_cpu.tv_sec*1000UL + st->reader_cpu.tv_nsec/1000000UL;
uint64_t background_cpu_millis = (uint64_t)st->background_cpu.tv_sec*1000UL + st->background_cpu.tv_nsec/1000000UL;
p += snprintf(p, end-p,
",\"cpr\":{\"global_ok\":%u"
",\"global_bad\":%u"
",\"global_skipped\":%u"
",\"local_ok\":%u"
",\"local_skipped\":%u"
",\"filtered\":%u}"
",\"cpu\":{\"demod\":%llu,\"reader\":%llu,\"background\":%llu}"
",\"messages\":%u}",
st->cpr_global_ok,
st->cpr_global_bad,
st->cpr_global_skipped,
st->cpr_local_ok,
st->cpr_local_skipped,
st->cpr_filtered,
(unsigned long long)demod_cpu_millis,
(unsigned long long)reader_cpu_millis,
(unsigned long long)background_cpu_millis,
st->messages_total);
}
return p;
}
char *generateStatsJson(const char *url_path, int *len) {
struct stats add;
char *buf = (char *) malloc(4096), *p = buf, *end = buf + 4096;
MODES_NOTUSED(url_path);
p += snprintf(p, end-p, "{\n");
p = appendStatsJson(p, end, &Modes.stats_current, "latest");
p += snprintf(p, end-p, ",\n");
p = appendStatsJson(p, end, &Modes.stats_1min[Modes.stats_latest_1min], "last1min");
p += snprintf(p, end-p, ",\n");
p = appendStatsJson(p, end, &Modes.stats_5min, "last5min");
p += snprintf(p, end-p, ",\n");
p = appendStatsJson(p, end, &Modes.stats_15min, "last15min");
p += snprintf(p, end-p, ",\n");
add_stats(&Modes.stats_alltime, &Modes.stats_current, &add);
p = appendStatsJson(p, end, &add, "total");
p += snprintf(p, end-p, "\n}\n");
assert(p <= end);
*len = p-buf;
return buf;
}
//
// Return a description of the receiver in json.
//
char *generateReceiverJson(const char *url_path, int *len)
{
char *buf = (char *) malloc(1024), *p = buf;
int history_size;
MODES_NOTUSED(url_path);
// work out number of valid history entries
if (Modes.json_aircraft_history[HISTORY_SIZE-1].content == NULL)
history_size = Modes.json_aircraft_history_next;
else
history_size = HISTORY_SIZE;
p += sprintf(p, "{ " \
"\"version\" : \"%s\", "
"\"refresh\" : %d, "
"\"history\" : %d",
MODES_DUMP1090_VERSION, Modes.json_interval * 1000, history_size);
if (Modes.json_location_accuracy && (Modes.fUserLat != 0.0 || Modes.fUserLon != 0.0)) {
if (Modes.json_location_accuracy == 1) {
p += sprintf(p, ", " \
"\"lat\" : %.2f, "
"\"lon\" : %.2f",
Modes.fUserLat, Modes.fUserLon); // round to 2dp - about 0.5-1km accuracy - for privacy reasons
} else {
p += sprintf(p, ", " \
"\"lat\" : %.6f, "
"\"lon\" : %.6f",
Modes.fUserLat, Modes.fUserLon); // exact location
}
}
p += sprintf(p, " }\n");
*len = (p - buf);
return buf;
}
char *generateHistoryJson(const char *url_path, int *len)
{
int history_index = -1;
if (sscanf(url_path, "/data/history_%d.json", &history_index) != 1)
return NULL;
if (history_index < 0 || history_index >= HISTORY_SIZE)
return NULL;
if (!Modes.json_aircraft_history[history_index].content)
return NULL;
*len = Modes.json_aircraft_history[history_index].clen;
return strdup(Modes.json_aircraft_history[history_index].content);
}
// Write JSON to file
void writeJsonToFile(const char *file, char * (*generator) (const char *,int*))
{
#ifndef _WIN32
char pathbuf[PATH_MAX];
char tmppath[PATH_MAX];
int fd;
int len = 0;
mode_t mask;
char *content;
if (!Modes.json_dir)
return;
snprintf(tmppath, PATH_MAX, "%s/%s.XXXXXX", Modes.json_dir, file);
tmppath[PATH_MAX-1] = 0;
fd = mkstemp(tmppath);
if (fd < 0)
return;
mask = umask(0);
umask(mask);
fchmod(fd, 0644 & ~mask);
snprintf(pathbuf, PATH_MAX, "/data/%s", file);
pathbuf[PATH_MAX-1] = 0;
content = generator(pathbuf, &len);
if (write(fd, content, len) != len)
goto error_1;
if (close(fd) < 0)
goto error_2;
snprintf(pathbuf, PATH_MAX, "%s/%s", Modes.json_dir, file);
pathbuf[PATH_MAX-1] = 0;
rename(tmppath, pathbuf);
free(content);
return;
error_1:
close(fd);
error_2:
unlink(tmppath);
free(content);
return;
#endif
}
//
//=========================================================================
//
#define MODES_CONTENT_TYPE_HTML "text/html;charset=utf-8"
#define MODES_CONTENT_TYPE_CSS "text/css;charset=utf-8"
#define MODES_CONTENT_TYPE_JSON "application/json;charset=utf-8"
#define MODES_CONTENT_TYPE_JS "application/javascript;charset=utf-8"
#define MODES_CONTENT_TYPE_GIF "image/gif"
static struct {
char *path;
char * (*handler)(const char*,int*);
char *content_type;
int prefix;
} url_handlers[] = {
{ "/data/aircraft.json", generateAircraftJson, MODES_CONTENT_TYPE_JSON, 0 },
{ "/data/receiver.json", generateReceiverJson, MODES_CONTENT_TYPE_JSON, 0 },
{ "/data/stats.json", generateStatsJson, MODES_CONTENT_TYPE_JSON, 0 },
{ "/data/history_", generateHistoryJson, MODES_CONTENT_TYPE_JSON, 1 },
{ NULL, NULL, NULL, 0 }
};
//
// Get an HTTP request header and write the response to the client.
// gain here we assume that the socket buffer is enough without doing
// any kind of userspace buffering.
//
// Returns 1 on error to signal the caller the client connection should
// be closed.
//
int handleHTTPRequest(struct client *c, char *p) {
char hdr[512];
int clen, hdrlen;
int httpver, keepalive;
int statuscode = 500;
const char *statusmsg = "Internal Server Error";
char *url, *content = NULL;
char *ext;
char *content_type;
int i;
if (Modes.debug & MODES_DEBUG_NET)
printf("\nHTTP request: %s\n", c->buf);
// Minimally parse the request.
httpver = (strstr(p, "HTTP/1.1") != NULL) ? 11 : 10;
if (httpver == 10) {
// HTTP 1.0 defaults to close, unless otherwise specified.
//keepalive = strstr(p, "Connection: keep-alive") != NULL;
} else if (httpver == 11) {
// HTTP 1.1 defaults to keep-alive, unless close is specified.
//keepalive = strstr(p, "Connection: close") == NULL;
}
keepalive = 0;
// Identify he URL.
p = strchr(p,' ');
if (!p) return 1; // There should be the method and a space
url = ++p; // Now this should point to the requested URL
p = strchr(p, ' ');
if (!p) return 1; // There should be a space before HTTP/
*p = '\0';
if (Modes.debug & MODES_DEBUG_NET) {
printf("\nHTTP keep alive: %d\n", keepalive);
printf("HTTP requested URL: %s\n\n", url);
}
// Ditch any trailing query part (AJAX might add one to avoid caching)
p = strchr(url, '?');
if (p) *p = 0;
statuscode = 404;
statusmsg = "Not Found";
for (i = 0; url_handlers[i].path; ++i) {
if ((url_handlers[i].prefix && !strncmp(url, url_handlers[i].path, strlen(url_handlers[i].path))) ||
(!url_handlers[i].prefix && !strcmp(url, url_handlers[i].path))) {
content_type = url_handlers[i].content_type;
content = url_handlers[i].handler(url, &clen);
if (!content)
continue;
statuscode = 200;
statusmsg = "OK";
if (Modes.debug & MODES_DEBUG_NET) {
printf("HTTP: 200: %s -> internal (%d bytes, %s)\n", url, clen, content_type);
}
break;
}
}
if (!content) {
struct stat sbuf;
int fd = -1;
char rp[PATH_MAX], hrp[PATH_MAX];
char getFile[1024];
if (strlen(url) < 2) {
snprintf(getFile, sizeof getFile, "%s/gmap.html", HTMLPATH); // Default file
} else {
snprintf(getFile, sizeof getFile, "%s/%s", HTMLPATH, url);
}
if (!realpath(getFile, rp))
rp[0] = 0;
if (!realpath(HTMLPATH, hrp))
strcpy(hrp, HTMLPATH);
clen = -1;
content = strdup("Server error occured");
if (!strncmp(hrp, rp, strlen(hrp))) {
if (stat(getFile, &sbuf) != -1 && (fd = open(getFile, O_RDONLY)) != -1) {
content = (char *) realloc(content, sbuf.st_size);
if (read(fd, content, sbuf.st_size) != -1) {
clen = sbuf.st_size;
statuscode = 200;
statusmsg = "OK";
}
}
} else {
errno = ENOENT;
}
if (clen < 0) {
content = realloc(content, 128);
clen = snprintf(content, 128,"Error opening HTML file: %s", strerror(errno));
statuscode = 404;
statusmsg = "Not Found";
}
if (fd != -1) {
close(fd);
}
// Get file extension and content type
content_type = MODES_CONTENT_TYPE_HTML; // Default content type
ext = strrchr(getFile, '.');
if (strlen(ext) > 0) {
if (strstr(ext, ".json")) {
content_type = MODES_CONTENT_TYPE_JSON;
} else if (strstr(ext, ".css")) {
content_type = MODES_CONTENT_TYPE_CSS;
} else if (strstr(ext, ".js")) {
content_type = MODES_CONTENT_TYPE_JS;
} else if (strstr(ext, ".gif")) {
content_type = MODES_CONTENT_TYPE_GIF;
}
}
if (Modes.debug & MODES_DEBUG_NET) {
printf("HTTP: %d %s: %s -> %s (%d bytes, %s)\n", statuscode, statusmsg, url, rp, clen, content_type);
}
}
// Create the header and send the reply
hdrlen = snprintf(hdr, sizeof(hdr),
"HTTP/1.1 %d %s\r\n"
"Server: Dump1090\r\n"
"Content-Type: %s\r\n"
"Connection: %s\r\n"
"Content-Length: %d\r\n"
"Cache-Control: no-cache, must-revalidate\r\n"
"Expires: Sat, 26 Jul 1997 05:00:00 GMT\r\n"
"\r\n",
statuscode, statusmsg,
content_type,
keepalive ? "keep-alive" : "close",
clen);
if (Modes.debug & MODES_DEBUG_NET) {
printf("HTTP Reply header:\n%s", hdr);
}
// Send header and content.
#ifndef _WIN32
if ( (write(c->fd, hdr, hdrlen) != hdrlen)
|| (write(c->fd, content, clen) != clen) ) {
#else
if ( (send(c->fd, hdr, hdrlen, 0) != hdrlen)
|| (send(c->fd, content, clen, 0) != clen) ) {
#endif
free(content);
return 1;
}
free(content);
Modes.stats_current.http_requests++;
return !keepalive;
}
//
//=========================================================================
//
// This function polls the clients using read() in order to receive new
// messages from the net.
//
// The message is supposed to be separated from the next message by the
// separator 'sep', which is a null-terminated C string.
//
// Every full message received is decoded and passed to the higher layers
// calling the function's 'handler'.
//
// The handler returns 0 on success, or 1 to signal this function we should
// close the connection with the client in case of non-recoverable errors.
//
void modesReadFromClient(struct client *c, char *sep,
int(*handler)(struct client *, char *)) {
int left;
int nread;
int fullmsg;
int bContinue = 1;
char *s, *e, *p;
while(bContinue) {
fullmsg = 0;
left = MODES_CLIENT_BUF_SIZE - c->buflen;
// If our buffer is full discard it, this is some badly formatted shit
if (left <= 0) {
c->buflen = 0;
left = MODES_CLIENT_BUF_SIZE;
// If there is garbage, read more to discard it ASAP
}
#ifndef _WIN32
nread = read(c->fd, c->buf+c->buflen, left);
#else
nread = recv(c->fd, c->buf+c->buflen, left, 0);
if (nread < 0) {errno = WSAGetLastError();}
#endif
// If we didn't get all the data we asked for, then return once we've processed what we did get.
if (nread != left) {
bContinue = 0;
}
if (nread == 0) { // End of file
modesCloseClient(c);
return;
}
#ifndef _WIN32
if (nread < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { // No data available (not really an error)
#else
if (nread < 0 && errno == EWOULDBLOCK) { // No data available (not really an error)
#endif
return;
}
if (nread < 0) { // Other errors
modesCloseClient(c);
return;
}
c->buflen += nread;
// Always null-term so we are free to use strstr() (it won't affect binary case)
c->buf[c->buflen] = '\0';
e = s = c->buf; // Start with the start of buffer, first message
if (c->service == Modes.bis) {
// This is the Beast Binary scanning case.
// If there is a complete message still in the buffer, there must be the separator 'sep'
// in the buffer, note that we full-scan the buffer at every read for simplicity.
left = c->buflen; // Length of valid search for memchr()
while (left && ((s = memchr(e, (char) 0x1a, left)) != NULL)) { // The first byte of buffer 'should' be 0x1a
s++; // skip the 0x1a
if (*s == '1') {
e = s + MODEAC_MSG_BYTES + 8; // point past remainder of message
} else if (*s == '2') {
e = s + MODES_SHORT_MSG_BYTES + 8;
} else if (*s == '3') {
e = s + MODES_LONG_MSG_BYTES + 8;
} else {
e = s; // Not a valid beast message, skip
left = &(c->buf[c->buflen]) - e;
continue;
}
// we need to be careful of double escape characters in the message body
for (p = s; p < e; p++) {
if (0x1A == *p) {
p++; e++;
if (e > &(c->buf[c->buflen])) {
break;
}
}
}
left = &(c->buf[c->buflen]) - e;
if (left < 0) { // Incomplete message in buffer
e = s - 1; // point back at last found 0x1a.
break;
}
// Have a 0x1a followed by 1, 2 or 3 - pass message less 0x1a to handler.
if (handler(c, s)) {
modesCloseClient(c);
return;
}
fullmsg = 1;
}
s = e; // For the buffer remainder below
} else {
//
// This is the ASCII scanning case, AVR RAW or HTTP at present
// If there is a complete message still in the buffer, there must be the separator 'sep'
// in the buffer, note that we full-scan the buffer at every read for simplicity.
//
while ((e = strstr(s, sep)) != NULL) { // end of first message if found
*e = '\0'; // The handler expects null terminated strings
if (handler(c, s)) { // Pass message to handler.
modesCloseClient(c); // Handler returns 1 on error to signal we .
return; // should close the client connection
}
s = e + strlen(sep); // Move to start of next message
fullmsg = 1;
}
}
if (fullmsg) { // We processed something - so
c->buflen = &(c->buf[c->buflen]) - s; // Update the unprocessed buffer length
memmove(c->buf, s, c->buflen); // Move what's remaining to the start of the buffer
} else { // If no message was decoded process the next client
return;
}
}
}
#define TSV_MAX_PACKET_SIZE 160
static void writeFATSV() {
struct aircraft *a;
time_t now;
static time_t lastTime = 0;
if (!Modes.fatsv_out.connections) {
return; // no active connections
}
now = time(NULL);
if (now <= lastTime) {
// scan once a second at most
return;
}
lastTime = now;
for (a = Modes.aircrafts; a; a = a->next) {
int altValid = 0;
int alt = 0;
int groundValid = 0;
int ground = 0;
int latlonValid = 0;
int useful = 0;
int emittedSecondsAgo;
char *p, *end;
// skip non-ICAO
if (a->addr & MODES_NON_ICAO_ADDRESS)
continue;
if (a->messages < 2) // basic filter for bad decodes
continue;
// don't emit if it hasn't updated since last time
if (a->seen < a->fatsv_last_emitted) {
continue;
}
emittedSecondsAgo = (int)(now - a->fatsv_last_emitted);
// don't emit more than once every five seconds
if (emittedSecondsAgo < 5) {
continue;
}
if (a->bFlags & MODES_ACFLAGS_ALTITUDE_VALID) {
altValid = 1;
alt = a->altitude;
}
if (a->bFlags & MODES_ACFLAGS_AOG_VALID) {
groundValid = 1;
if (a->bFlags & MODES_ACFLAGS_AOG) {
alt = 0;
ground = 1;
}
}
if (a->bFlags & MODES_ACFLAGS_LATLON_VALID) {
latlonValid = 1;
}
// if it's over 10,000 feet, don't emit more than once every 10 seconds
if (alt > 10000 && emittedSecondsAgo < 10) {
continue;
}
// disable if you want only ads-b
// also don't send mode S very often
if (!latlonValid) {
if (emittedSecondsAgo < 30) {
continue;
}
} else {
// if it hasn't changed altitude very much and it hasn't changed
// heading very much, don't update real often
if (abs(a->track - a->fatsv_emitted_track) < 2 && abs(alt - a->fatsv_emitted_altitude) < 50) {
if (alt < 10000) {
// it hasn't changed much but we're below 10,000 feet
// so update more frequently
if (emittedSecondsAgo < 10) {
continue;
}
} else {
// above 10,000 feet, don't update so often when it
// hasn't changed much
if (emittedSecondsAgo < 30) {
continue;
}
}
}
}
p = prepareWrite(&Modes.fatsv_out, TSV_MAX_PACKET_SIZE);
if (!p)
return;
end = p + TSV_MAX_PACKET_SIZE;
# define bufsize(_p,_e) ((_p) >= (_e) ? (size_t)0 : (size_t)((_e) - (_p)))
p += snprintf(p, bufsize(p,end), "clock\t%ld\thexid\t%06X", a->seen, a->addr);
if (*a->flight != '\0') {
p += snprintf(p, bufsize(p,end), "\tident\t%s", a->flight);
}
if (a->bFlags & MODES_ACFLAGS_SQUAWK_VALID) {
p += snprintf(p, bufsize(p,end), "\tsquawk\t%04x", a->modeA);
}
if (altValid) {
p += snprintf(p, bufsize(p,end), "\talt\t%d", alt);
useful = 1;
}
if (a->bFlags & MODES_ACFLAGS_SPEED_VALID) {
p += snprintf(p, bufsize(p,end), "\tspeed\t%d", a->speed);
useful = 1;
}
if (groundValid) {
if (ground) {
p += snprintf(p, bufsize(p,end), "\tairGround\tG");
} else {
p += snprintf(p, bufsize(p,end), "\tairGround\tA");
}
}
if (latlonValid) {
p += snprintf(p, bufsize(p,end), "\tlat\t%.5f\tlon\t%.5f", a->lat, a->lon);
useful = 1;
}
if (a->bFlags & MODES_ACFLAGS_HEADING_VALID) {
p += snprintf(p, bufsize(p,end), "\theading\t%d", a->track);
useful = 1;
}
// if we didn't get at least an alt or a speed or a latlon or
// a heading, bail out. We don't need to do anything special
// to unwind prepareWrite().
if (!useful) {
continue;
}
p += snprintf(p, bufsize(p,end), "\n");
if (p <= end)
completeWrite(&Modes.fatsv_out, p);
else
fprintf(stderr, "fatsv: output too large (max %d, overran by %d)\n", TSV_MAX_PACKET_SIZE, (int) (p - end));
# undef bufsize
a->fatsv_last_emitted = now;
a->fatsv_emitted_altitude = alt;
a->fatsv_emitted_track = a->track;
}
}
//
// Perform periodic network work
//
void modesNetPeriodicWork(void) {
struct client *c, **prev;
time_t now = time(NULL);
int j;
int need_heartbeat = 0, need_flush = 0;
// Accept new connetions
modesAcceptClients();
// Read from clients
for (c = Modes.clients; c; c = c->next) {
if (c->service == Modes.ris) {
modesReadFromClient(c,"\n",decodeHexMessage);
} else if (c->service == Modes.bis) {
modesReadFromClient(c,"",decodeBinMessage);
} else if (c->service == Modes.https) {
modesReadFromClient(c,"\r\n\r\n",handleHTTPRequest);
}
}
// Generate FATSV output
writeFATSV();
// If we have generated no messages for a while, generate
// a dummy heartbeat message.
if (Modes.net_heartbeat_interval) {
for (j = 0; j < MODES_NET_SERVICES_NUM; j++) {
if (services[j].writer &&
services[j].writer->connections &&
(services[j].writer->lastWrite + Modes.net_heartbeat_interval) <= now) {
need_flush = 1;
if (services[j].writer->dataUsed == 0) {
need_heartbeat = 1;
break;
}
}
}
}
if (need_heartbeat) {
//
// We haven't sent any traffic for some time. To try and keep any TCP
// links alive, send a null frame. This will help stop any routers discarding our TCP
// link which will cause an un-recoverable link error if/when a real frame arrives.
//
// Fudge up a null message
struct modesMessage mm;
memset(&mm, 0, sizeof(mm));
mm.msgbits = MODES_SHORT_MSG_BITS;
mm.timestampMsg = 0;
mm.msgtype = -1;
// Feed output clients
modesQueueOutput(&mm);
}
// If we have data that has been waiting to be written for a while,
// write it now.
for (j = 0; j < MODES_NET_SERVICES_NUM; j++) {
if (services[j].writer &&
services[j].writer->dataUsed &&
(need_flush || (services[j].writer->lastWrite + Modes.net_output_flush_interval) <= now)) {
flushWrites(services[j].writer);
}
}
// Unlink and free closed clients
for (prev = &Modes.clients, c = *prev; c; c = *prev) {
if (c->fd == -1) {
// Recently closed, prune from list
*prev = c->next;
free(c);
} else {
prev = &c->next;
}
}
}
//
// =============================== Network IO ===========================
//