q//i and t/w filter infrastructure support.

git-svn-id: http://repo.ham.fi/svn/aprsc/trunk@211 3ce903b1-3385-4e86-93cd-f9a4a239f7ac
This commit is contained in:
Matti Aarnio 2008-03-22 05:32:40 +00:00
parent 181abd575c
commit be9ea9d8bd
5 changed files with 303 additions and 100 deletions

View File

@ -285,6 +285,7 @@ int main(int argc, char **argv)
cleanup_tick += 60;
historydb_cleanup();
filter_wx_cleanup();
filter_entrycall_cleanup();
}
}
@ -301,6 +302,7 @@ int main(int argc, char **argv)
free_config();
dupecheck_atend();
historydb_atend();
filter_wx_atend();
filter_entrycall_atend();
hlog(LOG_CRIT, "Shut down.");

View File

@ -19,7 +19,7 @@
*
*/
// FIXME: filters: s
// FIXME: write filters: s
#include <string.h>
#include <strings.h>
@ -123,10 +123,7 @@ exclusion then the packet is not passed no matter any other filter definitions.
/* values above are chosen for 4 byte alignment.. */
struct filter_refcallsign_t {
union {
char callsign[CALLSIGNLEN_MAX+1]; /* size: 10.. */
int32_t cc[2]; /* makes alignment! // size: 8 */
}; /* ANONYMOUS UNION */
char callsign[CALLSIGNLEN_MAX+1]; /* size: 10.. */
int8_t reflen; /* length and flags */
};
struct filter_head_t {
@ -138,8 +135,9 @@ struct filter_head_t {
#define f_coslat f_lonW /* for R filter */
char type; /* 1 char */
int8_t negation; /* boolean flag */
int16_t numnames; /* used as named, and as bit-set on T_*** enumerations */
int16_t negation; /* boolean flag */
int16_t numnames; /* used as named, and as cache validity flag */
int16_t bitflags; /* used as bit-set on T_*** enumerations */
union {
/* for cases where there is only one.. */
struct filter_refcallsign_t refcallsign;
@ -174,11 +172,16 @@ struct filter_entrycall_t {
time_t expirytime;
uint32_t hash;
int len;
union {
char callsign[CALLSIGNLEN_MAX+1];
int32_t cc[2]; /* makes alignment */
}; /* ANONYMOUS UNION */
char qcode; /* q-construct code */
char callsign[CALLSIGNLEN_MAX+1];
char qcode; /* q-construct code */
};
struct filter_wx_t {
struct filter_wx_t *next;
time_t expirytime;
uint32_t hash;
int len;
char callsign[CALLSIGNLEN_MAX+1];
};
typedef enum {
@ -187,22 +190,35 @@ typedef enum {
MatchWild
} MatchEnum;
#define FILTER_ENTRYCALL_HASHSIZE 2048 /* Around 500-600 in db,
this looks for collision free result.. */
#define FILTER_ENTRYCALL_HASHSIZE 2048 /* Around 500-600 in db, this looks
for collision free result.. */
rwlock_t filter_entrycall_rwlock;
int filter_entrycall_maxage = 60*60; /* 1 hour, default. Validity on lookups: 5 minutes less.. */
int filter_entrycall_maxage = 60*60; /* 1 hour, default. Validity on
lookups: 5 minutes less.. */
struct filter_entrycall_t *filter_entrycall_hash[FILTER_ENTRYCALL_HASHSIZE];
#define FILTER_WX_HASHSIZE 1024 /* Around 300-400 in db, this looks
for collision free result.. */
rwlock_t filter_wx_rwlock;
int filter_wx_maxage = 60*60; /* 1 hour, default. Validity on
lookups: 5 minutes less.. */
struct filter_wx_t *filter_wx_hash[FILTER_WX_HASHSIZE];
#ifndef _FOR_VALGRIND_
cellarena_t *filter_cells;
cellarena_t *filter_entrycall_cells;
cellarena_t *filter_wx_cells;
#endif
int hist_lookup_interval = 2; /* Cache lookups this much seconds on the filter entry */
int hist_lookup_interval = 20; /* FIXME: Configurable: Cache historydb
position lookups this much seconds on
each filter entry referring to some
fixed callsign (f,m,t) */
float filter_lat2rad(float lat)
@ -234,6 +250,12 @@ void filter_init(void)
CELLMALLOC_POLICY_FIFO,
32 /* 32 kB at the time */,
0 /* minfree */ );
filter_wx_cells = cellinit( sizeof(struct filter_wx_t),
__alignof__(struct filter_wx_t),
CELLMALLOC_POLICY_FIFO,
32 /* 32 kB at the time */,
0 /* minfree */ );
#endif
}
@ -261,12 +283,24 @@ static void filter_entrycall_free(struct filter_entrycall_t *f)
* with gcc builtin optimizers.
*/
int filter_entrycall_insert(const char *key, const int keylen, const char qcons)
int filter_entrycall_insert(struct pbuf_t *pb)
{
struct filter_entrycall_t *f, **fp, *f2;
/* OK, pre-parsing produced accepted result */
uint32_t hash;
int idx;
int idx, keylen;
const char qcons = pb->qconst_start[2];
const char *key = pb->qconst_start+4;
for (keylen = 0; keylen <= CALLSIGNLEN_MAX; ++keylen) {
int c = key[keylen];
if (c == ',' || c == ':')
break;
}
if ((key[keylen] != ',' && key[keylen] != ':') || keylen < CALLSIGNLEN_MIN)
return 0; /* Bad entry-station callsign */
pb->entrycall_len = keylen; // FIXME: should be in incoming parser...
/* We insert only those that have Q-Constructs of qAR or qAr */
if (qcons != 'r' && qcons != 'R') return 0;
@ -334,10 +368,13 @@ int filter_entrycall_insert(const char *key, const int keylen, const char qcons)
* with gcc builtin optimizers.
*/
static int filter_entrycall_lookup(const char *key, const int keylen)
static int filter_entrycall_lookup(const struct pbuf_t *pb)
{
struct filter_entrycall_t *f, **fp, *f2;
uint32_t hash = keyhash(key, keylen, 0);
const char *key = pb->qconst_start+4;
const int keylen = pb->entrycall_len;
uint32_t hash = keyhash(key, keylen, 0);
int idx = (hash ^ (hash >> 16)) % FILTER_ENTRYCALL_HASHSIZE; /* Fold the hashbits.. */
f2 = NULL;
@ -425,6 +462,177 @@ void filter_entrycall_atend(void)
static void filter_wx_free(struct filter_wx_t *f)
{
#ifndef _FOR_VALGRIND_
cellfree( filter_wx_cells, f );
#else
hfree(f);
#endif
}
/*
* The filter_wx_insert() does lookup key storage for problem of:
*
* Positionless T_WX packets want also position packets on output filters.
*/
int filter_wx_insert(struct pbuf_t *pb)
{
struct filter_wx_t *f, **fp, *f2;
/* OK, pre-parsing produced accepted result */
const char *key = pb->data;
const int keylen = pb->srccall_end - key;
uint32_t hash;
int idx;
if (!((pb->packettype & T_WX) && !(pb->flags & F_HASPOS)))
return 0; // Not a WX packet without position, not intrerested
hash = keyhash(key, keylen, 0);
idx = (hash ^ (hash >> 16)) % FILTER_WX_HASHSIZE; /* Fold the hashbits.. */
rwl_wrlock(&filter_wx_rwlock);
fp = &filter_wx_hash[idx];
f2 = NULL;
while (( f = *fp )) {
if ( f->hash == hash ) {
if (f->len == keylen) {
int cmp = memcmp(f->callsign, key, keylen);
if (cmp == 0) { /* Have key match */
f->expirytime = now + filter_wx_maxage;
f2 = f;
break;
}
}
}
/* No match at all, advance the pointer.. */
fp = &(f -> next);
}
if (!f2) {
/* Allocate and insert into hash table */
fp = &filter_wx_hash[idx];
#ifndef _FOR_VALGRIND_
f = cellmalloc(filter_wx_cells);
#else
f = hmalloc(sizeof(*f));
#endif
if (f) {
f->next = *fp;
f->expirytime = now + filter_wx_maxage;
f->hash = hash;
f->len = keylen;
memcpy(f->callsign, key, keylen);
memset(f->callsign+keylen, 0, sizeof(f->callsign)-keylen);
*fp = f2 = f;
}
}
rwl_wrunlock(&filter_wx_rwlock);
return 0;
}
static int filter_wx_lookup(const struct pbuf_t *pb)
{
struct filter_wx_t *f, **fp, *f2;
const char *key = pb->data;
const int keylen = pb->srccall_end - key;
uint32_t hash = keyhash(key, keylen, 0);
int idx = (hash ^ (hash >> 16)) % FILTER_WX_HASHSIZE; /* Fold the hashbits.. */
f2 = NULL;
rwl_rdlock(&filter_wx_rwlock);
fp = &filter_wx_hash[idx];
while (( f = *fp )) {
if ( f->hash == hash ) {
if (f->len == keylen) {
int rc = memcmp(f->callsign, key, keylen);
if (rc == 0) { /* Have key match, see if it is
still valid entry ? */
if (f->expirytime < now - 60) {
f2 = f;
break;
}
}
}
}
/* No match at all, advance the pointer.. */
fp = &(f -> next);
}
rwl_rdunlock(&filter_wx_rwlock);
return (f2 != NULL);
}
/*
* The filter_wx_cleanup() does purge old entries
* out of the database. Run about once a minute.
*/
void filter_wx_cleanup(void)
{
int k, cleancount = 0;
struct filter_wx_t *f, **fp;
rwl_wrlock(&filter_wx_rwlock);
for (k = 0; k < FILTER_WX_HASHSIZE; ++k) {
fp = & filter_wx_hash[k];
while (( f = *fp )) {
/* Did it expire ? */
if (f->expirytime <= now) {
*fp = f->next;
f->next = NULL;
filter_wx_free(f);
++cleancount;
continue;
}
/* No purge, advance the pointer.. */
fp = &(f -> next);
}
}
rwl_wrunlock(&filter_wx_rwlock);
hlog(LOG_DEBUG, "filter_wx_cleanup() removed %d entries", cleancount);
}
/*
* The filter_wx_atend() does purge all entries
* out of the database. Run at the exit of the program.
* This exists primarily to make valgrind happy...
*/
void filter_wx_atend(void)
{
int k;
struct filter_wx_t *f, **fp;
rwl_wrlock(&filter_wx_rwlock);
for (k = 0; k < FILTER_WX_HASHSIZE; ++k) {
fp = & filter_wx_hash[k];
while (( f = *fp )) {
*fp = f->next;
f->next = NULL;
filter_wx_free(f);
}
}
rwl_wrunlock(&filter_wx_rwlock);
}
/*
* filter_match_on_callsignset() matches prefixes, or exact keys
@ -631,7 +839,7 @@ int filter_parse(struct client_t *c, const char *filt, int is_user_filter)
}
f0.h.type = *filt;
if (!strchr("abdemopqrstuABDEMOPQRSTU",*filt)) {
if (!strchr("abdefmopqrstuABDEFMOPQRSTU",*filt)) {
/* Not valid filter code */
hlog(LOG_DEBUG, "Bad filter code: %s", filt0);
return -1;
@ -739,7 +947,7 @@ int filter_parse(struct client_t *c, const char *filt, int is_user_filter)
f0.h.refcallsign.callsign[CALLSIGNLEN_MAX] = 0;
f0.h.refcallsign.reflen = strlen(f0.h.refcallsign.callsign);
f0.h.numnames = 1;
f0.h.numnames = 0; // reusing this as "position-cache valid" flag
// hlog(LOG_DEBUG, "Filter: %s -> F xxx %.3f", filt0, f0.h.f_dist);
@ -759,13 +967,9 @@ int filter_parse(struct client_t *c, const char *filt, int is_user_filter)
hlog(LOG_DEBUG, "Bad filter parse: %s", filt0);
return -1;
}
f0.h.numnames = 0; // reusing this as "position-cache valid" flag
// hlog(LOG_DEBUG, "Filter: %s -> M %.3f", filt0, f0.h.f_dist);
f0.h.f_latN = filter_lat2rad(f0.h.f_latN);
f0.h.f_lonW = filter_lon2rad(f0.h.f_lonW);
f0.h.f_coslat = cosf( f0.h.f_latN ); /* Store pre-calculated COS of LAT */
break;
case 'o':
@ -798,8 +1002,7 @@ int filter_parse(struct client_t *c, const char *filt, int is_user_filter)
/* q/con/ana q Contruct filter */
s = filt+1;
f0.h.type = 'q';
/* re-use f0.h.numnames field for QC_** flags */
f0.h.numnames = 0;
f0.h.bitflags = 0; /* For QC_* flags */
if (*s++ != '/') {
hlog(LOG_DEBUG, "Bad q-filter parse: %s", filt0);
@ -808,34 +1011,34 @@ int filter_parse(struct client_t *c, const char *filt, int is_user_filter)
for ( ; *s && *s != '/'; ++s ) {
switch (*s) {
case 'C':
f0.h.numnames |= QC_C;
f0.h.bitflags |= QC_C;
break;
case 'X':
f0.h.numnames |= QC_X;
f0.h.bitflags |= QC_X;
break;
case 'U':
f0.h.numnames |= QC_U;
f0.h.bitflags |= QC_U;
break;
case 'o':
f0.h.numnames |= QC_o;
f0.h.bitflags |= QC_o;
break;
case 'O':
f0.h.numnames |= QC_O;
f0.h.bitflags |= QC_O;
break;
case 'S':
f0.h.numnames |= QC_S;
f0.h.bitflags |= QC_S;
break;
case 'r':
f0.h.numnames |= QC_r;
f0.h.bitflags |= QC_r;
break;
case 'R':
f0.h.numnames |= QC_R;
f0.h.bitflags |= QC_R;
break;
case 'Z':
f0.h.numnames |= QC_Z;
f0.h.bitflags |= QC_Z;
break;
case 'I':
f0.h.numnames |= QC_I;
f0.h.bitflags |= QC_I;
break;
default:
hlog(LOG_DEBUG, "Bad q-filter parse: %s", filt0);
@ -845,7 +1048,7 @@ int filter_parse(struct client_t *c, const char *filt, int is_user_filter)
if (*s == '/') { /* second format */
++s;
if (*s == 'i' || *s == 'I') {
f0.h.numnames |= QC_AnalyticsI;
f0.h.bitflags |= QC_AnalyticsI;
++s;
}
if (*s) {
@ -876,7 +1079,7 @@ int filter_parse(struct client_t *c, const char *filt, int is_user_filter)
return -2;
}
hlog(LOG_DEBUG, "Filter: %s -> R %.3f %.3f %.3f", filt0, f0.h.f_latN, f0.h.f_lonW, f0.h.f_dist);
// hlog(LOG_DEBUG, "Filter: %s -> R %.3f %.3f %.3f", filt0, f0.h.f_latN, f0.h.f_lonW, f0.h.f_dist);
f0.h.f_latN = filter_lat2rad(f0.h.f_latN);
f0.h.f_lonW = filter_lon2rad(f0.h.f_lonW);
@ -898,8 +1101,8 @@ int filter_parse(struct client_t *c, const char *filt, int is_user_filter)
*/
s = filt+1;
f0.h.type = 't';
/* re-use f0.h.numnames field for T_** flags */
f0.h.numnames = 0;
f0.h.bitflags = 0;
f0.h.numnames = 0; // reusing this as "position-cache valid" flag
if (*s++ != '/') {
hlog(LOG_DEBUG, "Bad filter parse: %s", filt0);
@ -908,37 +1111,37 @@ int filter_parse(struct client_t *c, const char *filt, int is_user_filter)
for ( ; *s && *s != '/'; ++s ) {
switch (*s) {
case '*':
f0.h.numnames |= ~T_CWOP; /* "ALL" -- excluding CWOP */
f0.h.bitflags |= ~T_CWOP; /* "ALL" -- excluding CWOP */
break;
case 'c': case 'C':
f0.h.numnames |= T_CWOP;
f0.h.bitflags |= T_CWOP;
break;
case 'i': case 'I':
f0.h.numnames |= T_ITEM;
f0.h.bitflags |= T_ITEM;
break;
case 'm': case 'M':
f0.h.numnames |= T_MESSAGE;
f0.h.bitflags |= T_MESSAGE;
break;
case 'n': case 'N':
f0.h.numnames |= T_NWS;
f0.h.bitflags |= T_NWS;
break;
case 'o': case 'O':
f0.h.numnames |= T_OBJECT;
f0.h.bitflags |= T_OBJECT;
break;
case 'p': case 'P':
f0.h.numnames |= T_POSITION;
f0.h.bitflags |= T_POSITION;
break;
case 'q': case 'Q':
f0.h.numnames |= T_QUERY;
f0.h.bitflags |= T_QUERY;
break;
case 's': case 'S':
f0.h.numnames |= T_STATUS;
f0.h.bitflags |= T_STATUS;
break;
case 't': case 'T':
f0.h.numnames |= T_TELEMETRY;
f0.h.bitflags |= T_TELEMETRY;
break;
case 'u': case 'U':
f0.h.numnames |= T_USERDEF;
f0.h.bitflags |= T_USERDEF;
break;
default:
hlog(LOG_DEBUG, "Bad filter parse: %s", filt0);
@ -1206,12 +1409,9 @@ static int filter_process_one_e(struct client_t *c, struct pbuf_t *pb, struct fi
struct filter_refcallsign_t ref;
const char *e = pb->qconst_start+4;
int i;
for (i = 0; i <= CALLSIGNLEN_MAX; ++i) {
if (e[i] == ',' || e[i] == ':')
break;
}
if ((e[i] != ',' && e[i] != ':') || i < CALLSIGNLEN_MIN)
int i = pb->entrycall_len;
if (i < 1) // should not happen..
return 0; /* Bad Entry-station callsign */
/* entry station address "qA*,addr," */
@ -1255,12 +1455,14 @@ static int filter_process_one_f(struct client_t *c, struct pbuf_t *pb, struct fi
/* find friend's last location packet */
if (f->h.hist_age < now) {
i = historydb_lookup( callsign, i, &history );
if (!i) return 0; /* no lookup result.. */
f->h.numnames = i;
f->h.hist_age = now + hist_lookup_interval;
if (!i) return 0; /* no lookup result.. */
f->h.f_latN = history->lat;
f->h.f_lonE = history->lon;
f->h.f_coslat = history->coslat;
}
if (!f->h.numnames) return 0; /* histdb lookup cache invalid */
lat1 = f->h.f_latN;
lon1 = f->h.f_lonE;
@ -1290,9 +1492,7 @@ static int filter_process_one_m(struct client_t *c, struct pbuf_t *pb, struct fi
OPTIMIZE! (21% of all filters!)
NOTE: MY RANGE is rarely moving, once there is a positional
fix, it could stay fixed... Or true historydb lookup frequency
could be limited to once per - say - every 100 seconds per any
given filter ? (wants time_t variable into filter...)
fix, it could stay fixed...
80-120 instances in APRS-IS core at any given time.
Up to 4200 invocations per second.
@ -1313,12 +1513,14 @@ static int filter_process_one_m(struct client_t *c, struct pbuf_t *pb, struct fi
if (f->h.hist_age < now) {
i = historydb_lookup( c->username, strlen(c->username), &history );
if (!i) return 0; /* no result */
f->h.hist_age = now + hist_lookup_interval;
f->h.numnames = i;
if (!i) return 0; /* no result */
f->h.f_latN = history->lat;
f->h.f_lonE = history->lon;
f->h.f_coslat = history->coslat;
}
if (!f->h.numnames) return 0; /* cached lookup invalid.. */
lat1 = f->h.f_latN;
lon1 = f->h.f_lonE;
@ -1355,25 +1557,9 @@ static int filter_process_one_o(struct client_t *c, struct pbuf_t *pb, struct fi
if ( (pb->packettype & (T_OBJECT|T_ITEM)) == 0 ) /* not an Object NOR Item */
return 0;
/* Pick object name ";item *" or ";item _" -- strip tail spaces
** Pick item name ")item!" or ")item_" -- keep all spaces
*/
/* FIXME? These filters are very rare in real use... */
/* FIXME: have parser to fill pb->objnamelen so this needs not to scan the buffer again. */
s = pb->info_start+1;
if (pb->packettype & T_OBJECT) { // It is an Object - No embedded spaces!
for (i = 0; i < CALLSIGNLEN_MAX; ++i, ++s) {
if (*s == ' ' || *s == '*' || *s == '_')
break;
}
} else { // It is an ITEM then..
for (i = 0; i < CALLSIGNLEN_MAX; ++i, ++s) {
if (*s == '!' || *s == '_') /* Embedded space are OK! */
break;
}
}
/* parse_aprs() has picked item/object name pointer and length.. */
s = pb->srcname;
i = pb->srcname_len;
if (i < 1) return 0; /* Bad object/item name */
/* object name */
@ -1471,22 +1657,14 @@ static int filter_process_one_q(struct client_t *c, struct pbuf_t *pb, struct fi
break;
}
if (f->h.numnames & mask) {
if (f->h.bitflags & mask) {
/* Something matched! */
return 1;
}
if (f->h.numnames & QC_AnalyticsI) {
/* Oh ? Analytical! Pick entry igate name..
if (f->h.bitflags & QC_AnalyticsI) {
/* Oh ? Analytical!
Has it ever been accepted into entry-igate database ? */
e += 2;
for (i = 0; i < CALLSIGNLEN_MAX; ++i) {
if (e[i] == ',' || e[i] == ':')
break;
}
if ((e[i] != ',' && e[i] != ':') || i < CALLSIGNLEN_MIN)
return 0; /* Bad Entry-station callsign */
if (filter_entrycall_lookup(e, i))
if (filter_entrycall_lookup(pb))
return 1; /* Found on entry-igate database! */
}
@ -1603,9 +1781,21 @@ static int filter_process_one_t(struct client_t *c, struct pbuf_t *pb, struct fi
("." is dummy addition for C comments..)
*/
int rc = 0;
if (pb->packettype & f->h.numnames) /* reused numnames as comparison bitmask */
if (pb->packettype & f->h.bitflags) /* bitflags as comparison bitmask */
rc = 1;
if (!rc && (f->h.bitflags & T_WX)) {
// "Note: The weather type filter also passes positions packets
// for positionless weather packets."
//
// 1) recognize positionless weather packets
// 2) register their source callsigns, do this in input_parse
// 3) when filtering for weather data, check non-weather recognized
// packets against the database in point 2
// 4) pass on packets matching point 3
rc = filter_wx_lookup(pb);
}
/* Either it stops here, or it continues... */
if (rc && f->h.type == 'T') { /* Within a range of callsign ?
@ -1632,6 +1822,7 @@ static int filter_process_one_t(struct client_t *c, struct pbuf_t *pb, struct fi
if (f->h.hist_age < now) {
i = historydb_lookup( callsign, callsignlen, &history );
f->h.numnames = i;
// hlog( LOG_DEBUG, "Type filter with callsign range used! call='%s', range=%.1f position %sfound",
// callsign, range, i ? "" : "not ");
@ -1643,6 +1834,7 @@ static int filter_process_one_t(struct client_t *c, struct pbuf_t *pb, struct fi
f->h.f_lonE = history->lon;
f->h.f_coslat = history->coslat;
}
if (!f->h.numnames) return 0; /* No valid data at range center position cache */
lat1 = f->h.f_latN;
lon1 = f->h.f_lonE;

View File

@ -31,10 +31,14 @@ extern void filter_free(struct filter_t *c);
extern int filter_process(struct worker_t *self, struct client_t *c, struct pbuf_t *pb);
extern int filter_commands(struct worker_t *self, struct client_t *c, const char *s, const int len);
extern int filter_entrycall_insert(const char *key, const int keylen, const char qcons);
extern int filter_entrycall_insert(struct pbuf_t *pb);
extern void filter_entrycall_cleanup(void);
extern void filter_entrycall_atend(void);
extern int filter_wx_insert(struct pbuf_t *pb);
extern void filter_wx_cleanup(void);
extern void filter_wx_atend(void);
extern float filter_lat2rad(float lat);
extern float filter_lon2rad(float lon);

View File

@ -516,6 +516,11 @@ int incoming_parse(struct worker_t *self, struct client_t *c, char *s, int len)
/* just try APRS parsing */
rc = parse_aprs(self, pb);
if (rc >= 0) {
filter_entrycall_insert(pb);
filter_wx_insert(pb);
}
/* put the buffer in the thread's incoming queue */
*self->pbuf_incoming_local_last = pb;
self->pbuf_incoming_local_last = &pb->next;

View File

@ -98,6 +98,7 @@ struct pbuf_t {
uint16_t flags; /* bitmask: one or more of F_* */
uint16_t srcname_len; /* parsed length of source (object, item, srcall) name 3..9 */
uint16_t dstcall_len; /* parsed length of destination callsign *including* SSID */
uint16_t entrycall_len;
int packet_len; /* the actual length of the packet, including CRLF */
int buf_len; /* the length of this buffer */
@ -113,7 +114,6 @@ struct pbuf_t {
float cos_lat; /* cache of COS of LATitude for radial distance filter */
char symbol[3]; /* 2(+1) chars of symbol, if any, NUL for not found */
char qcon; /* 1 char of q-construct for filters, NUL for not found */
char data[1]; /* contains the whole packet, including CRLF, ready to transmit */
};