This is an old revision of the document!
Table of Contents
PNMPTRACE - JSON to Packet Trace Converter for PNMP
What is PNMPTRACE?
PNMPTRACE is a free open-source command line program which converts PNMP-format packet traces from JSON to a familiar ASCII “trace” format.
What is PNMP?
PNMP is the Packet Network Monitoring Project. The PNMP server receives packet traces and status data from participating XRouter and BPQ nodes, for the purposes of network monotoring, analysis, fault-finding and planning.
The server, currently at 'node-api.packet.oarc.uk', makes the data available in a variety of forms, including an MQTT “hoseline” of raw data from the nodes (aka “reporters”). It is this data, from the endpoint at 'node-api.packet.oarc.uk/in/udp' that PNPMTRACE uses.
Requirements
- GCC if you want to compile the program from the source below.
- mosquitto_sub (or any other suitable MQTT client). You can install this using 'sudo apt install mosquitto-clients'
Executables
Notes
This document assumes the use of Linux, and the MQTT client 'mosquitto_sub', but any MQTT client which outputs JSON to stdout could be used instead.
How to Compile PNMPTRACE
- Copy and paste the C source code (below) into a file called pnmptrace.c
- Put pnmptrace.c into a directory of your choice.
- Open a terminal and change into that directory.
- Type: gcc -Wall -o “pnmptrace” “pnmptrace.c”
- Type: “ls” and you should see the compiled executable 'pnmptrace'.
You can leave the executable where it is, or move it to the /bin directory, which will allow it to be run from anywhere without prepending “./”. You can move the executable like this:
sudo mv pnmptrace /bin
How To Use PNMPTRACE
- Run mosquitto_sub, specifying the host and topic like so:
mosquitto_sub -h node-api.packet.oarc.uk -t in/udp
- You should see a constant stream of JSON data. If so, you can stop mosquitto_sub and move to the next step. If you don't see any JSON, stop mosquitto_sub and re-check your command.
- Pipe the output of mosquitto_sub into PNMPTRACE like this:
mosquitto_sub -h node-api.packet.oarc.uk -t in/udp | ./pnmptrace
(omit the "./" if you moved pnptrace to /bin)
You should now see the packet traces in a more familiar form. The exact format can be tweaked using various command line options, as detailed in the next section.
To read the MQTT from a file instead of mosquitto_sub, you would use a command like this, to pipe the output of “cat” into the input of pnmptrace:
cat mqtt.txt | ./pnmptrace
You may wish to apply some “filters” to restrict the amount of data being displayed. For instance, you might only be interested in UI frames, or frames from a particular station, or frames carrying a particular protocol such as IP.
Filters and Display Options
These are specified as arguments to the program. They cannot be changed on the fly.
Summary of Options
Option Description ----------------------------------------------------------------- -3 Don't trace NetRom layer 3 or above -4 Don't trace NetRom layer 4 or above -a <callsign> Show ALL frames to or from <callsign> -c Don't colourise the traces -C Include colour information in capture file -f <callsign> Show only frames addressed FROM <callsign> -h Show this message and exit -H Show header on separate line to trace -i Don't trace contents of INP3 routing unicasts -j Show the raw JSON before each trace -k Don't show L3RTT info field -l Suppress blank line between traces -n Don't trace contents of NetRom nodes broadcasts -o <file> Output trace to <file> -p <portnum> Show reports only from <portnum> -P <protocol> Show only frames with this L3 protocol -q No display when capturing to file (quiet) -r <callsign> Show reports only from <callsign> -s Suppress time stamp -t <callsign> Show only frames addressed TO <callsign> -T <frametype> Show only this AX25 frametype, e.g. "-T UI" -u Don't display UI frames -w <width> Display width (default 80 cols) -W Enable warnings of missing/bad JSON fields
More than one option can be specified, but some combinations are pointless. For example, if -3 is specified -i and -n are redundant.
Display Options:
Option Description
-----------------------------------------------------------------
-c
Don't colourise the traces. By default, traces are coloured
according to whether the link is RF or internet, and whether
the direction is "sent" or "received". RF-originated traces
are displayed in pure red (transmit) or green (receive).
Internet-originated traces are displayed in pink (transmit) or
turquoise (receive).
-C
Include colour information in capture file. This is off by
default, and is ignored if the '-c' option is specified.
Including colour information in the file allows it to be
played back in colour, but makes it harder to read with a text
editor.
-H
Show header (metadata) on a separate line to trace. This is
off by default, as most people seem to prefer "one line per
packet". If enabled, the display is less cryptic and includes
more information.
-j
Show the raw JSON prior to each trace. This is off by default.
If enabled, the JSON data for each trace is displayed first,
followed by the decoded trace. Included mainly for debugging.
-l
Suppress the blank line between traces. Off by default.
Normally a blank line is output between each packet trace for
clarity. However some people like a more cluttered display,
hence this option.
-o <filename>
Output the packet traces to <filename>. If enabled, everything
that is displayed on screen is echoed to a capture file, whose
path/name is specified by this option. The file is opened in
"overwrite" mode. While capturing, the screen output can be
suppressed using the '-q' (quiet) option below.
-q
Suppresses the display while capturing to file.
-s
Suppress the packet time stamp. Normally each packet trace
if prefixed with a time stamp of the form HH:MM:SS. These
timestamps are generated by the reporting nodes, not by the
server, so there may be time differences between them.
-w <width>
Specify the display width (default 80 columns). Most traces
should fit within 80 columns, but INP3 traces which include
several INP3 options might exceed this width. By default,
these are neatly line-wrapped to fit 80 columns. If you have a
wider display window you can specify the width using this
option. The display will then line-wrap to fit the specified
width. This is intended for displays wider than 80 columns,
not narrower.
-W
Enable warnings of missing or bad JSON fields. This is mainly
intended for debugging purposes.
Filter Options:
-3
Don't trace NetRom layer 3 or above. If this option is
specified, packets containing NetRom layer 3/4 information,
including NODES broadcasts and INP3 data, will end with
"NET/ROM" and won't be traced any further. You might use this
if for example you are only interested in the layer 2 data.
See also "-i", "-n" and "-4".
-4
Don't trace NetRom layer 4 or above. If this option is
specified, NetRom layer 4 headers or protocol extensions are
not traced.
-a <callsign>
Show ALL frames to or from <callsign>. If this filter is
specified, ONLY those frames which have AX25 source or
destination callsigns matching <callsign> are displayed. This
can be used to watch all traffic into or out of a specific
node.
-f <callsign>
Show only frames addressed FROM <callsign>. If this filter is
specified, ONLY those frames whose AX25 source callsign matches
<callsign> are displayed. This filter can be combined with
other options for even tighter filtering.
-i
Don't trace contents of INP3 routing unicasts. INP3 unicasts
can occupy a lot of space on screen. If you don't need to see
what they contain, the '-i' option suppresses decoding, so
all that is displayed is "NET/ROM INP3".
-k
Don't show L3RTT info field. The payload of an L3RTT frame can
be up to 236 bytes, much of which is empty space. This wraps
untidily, so the '-k' option can be used to suppress it.
-n
Don't trace contents of NetRom 'NODES' broadcasts. If this
option is used, nodes broadcasts display only "NET/ROM NODES".
-p <portnum>
Show reports only from <portnum>. This filter is intended for
use in conjunction with the '-r', '-t', '-f' or '-a' filters.
For example, "r G8PZT -p 5" would show everything received or
sent on G8PZT's port 5.
-P <protocol>
Show only frames with the specified L3 protocol. For example
"-P IP" shows only IP over AX25 frames. The recognised
protocols are as f0llows:
Mnemonic Meaning
--------------------------------------------------------
"SEG" Intermediate segment of a fragmented packet
"DATA" No layer 3, i.e. payload contains normal data
"NET/ROM" Payload contains NetRom/INP3 information
"IP" Payload contains IP datagram or part thereof
"ARP" Payload contains ARP data
"FLEXNET" Payload contains Flexnet protocol
"?" Unknown layer 3 protocol
-r <callsign>
Show reports only from <callsign>. This filters traffic by the
"reporter" callsign, not the AX25 "to" or "from" fields. For
example "-r G8PZT" shows only the frames sent or overheard by
node G8PZT.
-t <callsign>
Show only frames addressed TO <callsign>. If this filter is
specified, ONLY those frames whose AX25 destination callsign
matches <callsign> will be displayed. Note that the same frame
may be reported by more than one node.
-T <frametype>
Show only frames with the specified AX25 frametype. For example
"-T UI". The recognised frame types are as follows:
Mnemonic Meaning
---------------------------------------------------
"SABME" Set Asynchronous Balanced Mode Extended
"C" Non-extended connnect request (AKA SABM)
"D" Disconnect Request
"DM" Disconnected Mode / Busy
"UA" Unnumbered Acknowledgement
"UI" Unnumbered Information frame
"I" Numbered information frame
"FRMR" Frame Reject (serious error)
"RR" Receiver Ready
"RNR" Receiver Not Ready
"REJ" Reject (Frame not the expected one)
"SREJ" Selective Reject
"TEST" Test of data link
"XID" Exchange Identification
"?" Unknown type
-u
Don't display UI frames. This option may be useful if you
don't want to see beacons, APRS data, or nodes broadcasts.
Copyright © 2025 Paula Dowie
Source Code
/*
* PNMPTRACE - A JSON to AX25 Packet Trace Decoder for the experimental
* Packet Network Monitoring Project (PNMP).
*
* Copyright (C) 2025 Paula Dowie G8PZT.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 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
* <https://www.gnu.org/licenses/>.
*
***********************************************************************
*
* Purpose:
*
* This program reads serialised JSON data from stdin, and outputs
* it to stdout and/or file in a familiar "packet trace" format.
*
* The input JSON is expected to be in the PNMP (Packet Network
* Monitoring Project) format, as output by XRouter and BPQ nodes.
*
* The data source may be the output of an MQTT client, or a file
* containing previously downloaded JSON. The MQTT client may connect
* with the main PNMP server for a network-wide view, or XRouter's
* internal MQTT broker for local monitoring.
*
* The output format and packet filtering can be changed by command
* line options.
*
*
* Usage:
*
* pnpmtrace [options]
*
* (use the "-help" option to list options)
*
*
* Examples:
*
* cat mqtt.txt | pnmptrace -H -n
*
* mosquitto_sub -h node-api.packet.oarc.uk -t in/udp | pnmptrace
*
*
* Limitations:
*
* THIS IS ONLY A RUDIMENTARY JSON PARSER! It is sufficient for the
* purpose of decoding JSON from the Packet Network Monitoring
* Project server, and nothing else. One of the limitations is that
* it can not drill down into nested objects.
*
*
* Versions:
*
* Ver Date Comments
* -----------------------------------------------------------------
* 1.0 24/10/25 Quick and dirty proof of concept. Decodes only
* "L2Trace" objects. Requires an MQTT client such as
* "mosquitto_sub" to download the JSON from the
* server to stdout.
*
* To-Do:
*
* - Possibly include an MQTT client, to make this self-contained,
* although that would prevent the program from using other
* data sources.
*
* - Wildcard callsign filtering, if it would be any use?
*
* - Filtering on multiple callsigns, e.g. -t g8pzt*,KIDDER*,M1BFP-1
*
* - Filter by multiple frame types simultaneously, e.g. "-T I,UI"
*
* - Decode L3RTT frame payload.
*
* - Trace other report types when they have been implemented, e.g.
* where "@type" is "L3Trace", "L4Trace", "IpTrace" etc.
*
* - Maybe someone could convert this to RUST, SLIME, PLURP or
* whatever strange language is the flavour of the moment, because
* nobody understands "C" any more :-)
*
* Notes:
*
* This source is best viewed with a text editor such as geany or
* featherpad which colourises different code elements such as
* comments and strings.
*
* Yes I know JSON field names should ideally be case sensitive, but
* the source data isn't always consistent! Therefore case
* independent matching had to be used.
*
* The terms "field" and "object" may not be consistent. It was a
* single-afternoon project. Feel free to correct it!
*
* No attempt has been made to optimise the code, to make it
* efficient, or pretty. It just does the job it was intended for.
*
* Page width is 72 characters, as horizontal scrolling sucks.
*
* I *know* that my coding style is not industry standard, so don't
* bother telling me. If you don't like it, tough! It's all mine,
* and it works for me. If you can do better, why haven't you
* already done it? :-p
* */
#define _GNU_SOURCE // Required for strcasestr()
#ifdef WIN32
#include <windows.h>
#include <shlwapi.h>
#define strcasestr StrStrI
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <stdarg.h>
#include <ctype.h>
#include <time.h>
static char VERSION[] = "1.0";
static char Margin[] = "\n "; // Left margin for L3/L4 layers
/* If filters are populated, they restrict the display to frames whose
* fields match the filters. Unpopulated filters have no effect. The
* compiler should set all these to null strings
* */
static char ReportFilter [16]; // Callsign to accept reports from
static char SrcFilter [16]; // Source callsign filter
static char DstFilter [16]; // Destination callsign filter
static char AllFilter [16]; // Callsign to filter to/from
static char ProtoFilter [16]; // Protocol to filter by
static char TypeFilter [16]; // For filtering by L2Type
static int PortFilter = 0; // For filtering by port number
static int DisplayWidth = 80;
// TraceFlags control display options & filters
static int TraceFlags = 0x7ff;
#define TRACE_UI 0x01 // Unnumbered information frames (on)
#define TRACE_NETROM 0x02 // Trace Netrom L3/L4 layers (on)
#define TRACE_L3RTT 0x04 // Show Info field of L3RTT (on)
#define TRACE_NODES 0x08 // Trace into NODES broadcasts (on)
#define TRACE_INP3 0x10 // Trace into INP3 unicasts (on)
#define TRACE_L4 0x20 // Trace NetRom L4 headers (on)
#define TRACE_IP 0x40 // Trace IP headers (on)
#define TRACE_ARP 0x80 // Trace ARP packets (on)
#define TRACE_COLOR 0x100 // Trace in colour (on)
#define TRACE_STAMP 0x200 // Timestamp the trace (on)
#define TRACE_LBRK 0x400 // Line break between traces (on)
#define TRACE_HDRLIN 0x800 // Header & trace separate (off)
#define TRACE_JSON 0x1000 // Display JSON prior to trace (off)
#define TRACE_QUIET 0x2000 // Output to file only, no echo (off)
#define TRACE_COLOR2FILE 0x4000 // Send colour to file (off)
#define TRACE_WARNINGS 0x8000 // Display warnings of bad fields
static char CaptureFile [256]; // Capture file name
static FILE *FpCapture = NULL; // For capturing output to file
//######################################################################
// JSON FUNCTIONS
//######################################################################
/**********************************************************************/
/* Purpose: Find a named JSON object by name
* Called by: json_findArray() and json_getValue()
* Arguments: Pointer to serialised JSON object string. Object name.
* Actions: Performs a case-independent sliding match, looking for
* the object name (including surrounding quotes) in the
* serialised JSON. If found, the pointer is advanced over
* the name, the colon, and any whitespace, until the start
* of the object's value. If the object name is not found,
* or there is no colon after the nem, NULL is returned.
* Affects: Nothing
* Returns: Pointer to the start of the object's value in "json", or
* NULL if the object is not found.
* Notes: Name is case independent.
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
static char *json_findObject (const char *json, const char *name)
{
char tmp [80], *cp;
sprintf (tmp, "\"%s\"", name);
if ((cp = strcasestr (json, tmp)) == NULL)
return (NULL); // Name not found
cp++; // Skip opening quote of the name
while (*cp && *cp != '"') cp++; // find end of name
while (*cp && *cp != ':') cp++; // find the colon
if (*cp != ':') return (NULL); // No colon - so not a name
cp++; // skip the colon
while (isspace (*cp)) cp++; // Skip space after colon
return (cp); // Points at the object's value
}
/**********************************************************************/
/* Purpose: Find a named JSON array by name.
* Called by: trace_nodes() and trace_inp3().
* Arguments: Pointer to serialised JSON object string. Array name.
* Actions: Performs a case-independent sliding match looking for the
* array name in the serialised JSON. If found, checks that
* the name actually belongs to an array.
* Affects: Nothing.
* Returns: Pointer to the opening square bracket in the "json"
* string, or NULL if the array is not found.
* Notes: Name is case independent.
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
static char *json_findArray (const char *json, char *name)
{
char *cp;
if ((cp = json_findObject (json, name)) == NULL) return (NULL);
if (*cp != '[') return (NULL);
return (cp); // Point at opening bracket
}
/**********************************************************************/
/* Purpose: Get the value of a named JSON "field"
* Called by: Many places!
* Arguments: Pointer to serialised JSON object string, field name,
* Pointer to a string to receive the result, maximum chars
* to copy to result string.
* Actions: If the field is found, its string value, up to a maximum
* of "maxchars" is copied to the string pointed by "result"
* The quotes surrouunding string values are not copied.
* Affects: The string pointed by "result".
* Returns: Pointer to the first character after the field's value,
* or NULL if the field was not found.
* Notes: x
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
static char *json_getValue (const char *json, const char *name,
char *result, int maxlen)
{
char *cp;
int string = 0;
if ((cp = json_findObject (json, name)) == NULL)
return (NULL); // Name not found
if (*cp == '"') // If the first char of the value is a quote,
{
string = 1; // it's a string literal.
cp++; // Skip the quote character to point at value
}
if (string) // If it's a string literal
{
while (*cp && *cp != '"') // Copy everything between the quotes
{
if (maxlen-- > 0) *result++ = *cp;
cp++;
}
}
else // Not a string literal, probably number or boolean
{
// Copy the value to "result"
while (*cp && (*cp == '-' || *cp == '.' || isalnum (*cp)))
{
if (maxlen-- > 0) *result++ = *cp;
cp++;
}
}
*result = 0; // Terminate the result string
return (cp+1); // Pointer to first char AFTER the value
}
/**********************************************************************/
/* Purpose: Get next JSON object from an array of objects
* Called by: trace_inp3() only. Ought to be used by decde_nodes()!
* Arguments: Pointer to a string containing serialised array, pointer
* to a string buffer to receive the object, maximum number
* of characters to copy.
* Actions: Ignores curent object and finds start of next, then
* copies the found object into "buffer", including its
* opening and closing braces.
* Affects: Contents of the string pointed by "buffer".
* Returns: Pointer to the opening brace of the found array element,
* or NULL if no next object found.
* Notes: Flat arrays of simple objects only. Does not allow braces
* within object values.
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
static const char *json_getNextArrayElement (const char *json,
char *buffer, int maxlen)
{
const char *cp = json;
// Find end of current element
while (*cp && *cp != '}' && *cp != ']') cp++;
if (*cp != '}') return (NULL); // Didn't find end
// Find start of next element
while (*cp && *cp != '{' && *cp != ']') cp++;
if (*cp != '{') return (NULL); // No next element
json = cp; // Remember pointer to start of element
// Copy up to "maxlen" characters to "buffer", including braces.
while (*cp && maxlen-- > 0)
{
*buffer++ = *cp;
if (*cp == '}') break;
cp++;
}
*buffer = 0; // terminate the output string
return (json); // Pointer to start of element
}
//######################################################################
// PACKET TRACE FUNCTIONS
//######################################################################
/**********************************************************************/
/* Purpose: Output to user and optional capture file
* Called by: Most functions.
* Arguments: Format string plus zero or more additional fields
* Actions: Prints the data to a string, then outputs it to screen
* (stdout) and/or the capture file.
* Affects: stdout, capture file or both.
* Returns: Number of characters printed.
* Notes: The output string must not exceed 4095 bytes, but that is
* highly unlikely to happen. Most calls only output a few
* characters.
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
static int uprintf (char *fmt, ...)
{
char buff [4096];
va_list arg;
va_start (arg, fmt);
vsprintf (buff, fmt, arg);
va_end (arg);
// Output to capture file if it is open
if (FpCapture)
{
fputs (buff, FpCapture);
fflush (FpCapture);
}
// Output to stdio if not in "quiet" mode
if ((TraceFlags & TRACE_QUIET) == 0) fputs (buff, stdout);
return (strlen (buff));
}
/**********************************************************************/
/* Purpose: Decode and display a 'NODES' broadcast.
* Called by: trace_netromRoutingInfo() only.
* Arguments: Pointer to string containing serialised JSON object.
* Actions: Displays the alias of the node making the broadcast,
* then loops through the "nodes" array, displaying details
* of each route.
* Affects: stdout only
* Returns: None
* Notes: Assumes "fromAlias" appears before "nodes" in the JSON
* string. This is necessary because the JSON parser is
* crude and case insensitive. It cannot distinguish between
* the field *name* "nodes" and the field *value* "NODES"
* which appears earlier in the string. This could be fixed
* by a better parser.
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
static void trace_nodes (const char *json)
{
char tmp [80], *cp;
if ((TraceFlags & TRACE_NODES) == 0)
{
uprintf (" NODES Broadcast");
return; // Not wanted
}
if ((cp = json_getValue (json, "fromAlias", tmp, 6)) == NULL)
{
if (TraceFlags & TRACE_WARNINGS)
uprintf (" [missing 'fromAlias']");
return;
}
uprintf ("%sNODES Broadcast from %s:", Margin, tmp);
if ((cp = json_findArray (cp, "nodes")) == NULL)
{
if (TraceFlags & TRACE_WARNINGS)
uprintf (" [missing 'nodes' array]");
return;
}
// cp is pointing at the opening square bracket of nodes array
while (*cp)
{
// Format is "GE8PZT:BBS64 via GE8PZT qlty=20"
if (json_getValue (cp, "call", tmp, 9))
uprintf ("%s%s", Margin, tmp);
if (json_getValue (cp, "alias", tmp, 6))
uprintf (":%s", tmp);
if (json_getValue (cp, "via", tmp, 9))
uprintf (" via %s", tmp);
if (json_getValue (cp, "qual", tmp, 3))
uprintf (" qlty=%s", tmp);
while (*cp && *cp != '}') cp++; // find end of node object
if (*cp) cp++;
}
}
static int wrap (void)
{
uprintf ("%s ", Margin);
return (8);
}
/**********************************************************************/
/* Purpose: Decode and display an INP3 routing unicast
* Called by: trace_netromRoutingInfo() only.
* Arguments: Pointer to string containing serialised JSON object.
* Actions: Loops through the "nodes" array, displaying details
* of each route.
* Affects: stdout only.
* Returns: None
* Notes: x
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
static void trace_inp3 (const char *json)
{
char object [1024], tmp [80];
const char *cp;
if ((TraceFlags & TRACE_INP3) == 0)
{
uprintf (" INP3");
return;
}
uprintf ("%sINP3 Routing Unicast:", Margin);
if ((cp = json_findArray (json, "nodes")) == NULL)
{
if (TraceFlags & TRACE_WARNINGS)
uprintf (" [missing 'nodes' array]");
return;
}
// cp is now pointing at the opening square bracket of nodes array
while ((cp = json_getNextArrayElement (cp, object, 1023)) != NULL)
{
int cols = 0;
// Minimum format is "GB7BDH hp=2 tt=3"
if (json_getValue (object, "call", tmp, 9))
cols += uprintf ("%s%-9s", Margin, tmp);
if (json_getValue (object, "hops", tmp, 2))
cols += uprintf (" hp=%-2s", tmp);
if (json_getValue (object, "tt", tmp, 5))
cols += uprintf (" tt=%-5s", tmp);
// Optional fields
// "Alias=SWINDN 5128.75N 71582600.46E S/W=XRPi NODE PMS XRCHAT Ver=504k 25/10 06:20
if (json_getValue (object, "alias", tmp, 6))
cols += uprintf (" Alias=%-6s", tmp);
if (json_getValue (object, "latitude", tmp, 20))
cols += uprintf (" %s", tmp);
if (json_getValue (object, "longitude", tmp, 20))
cols += uprintf (" %s", tmp);
if (json_getValue (object, "software", tmp, 20))
cols += uprintf (" S/W=%s", tmp);
// If could overflow 80-col line after this point
if (json_getValue (object, "version", tmp, 10))
{
if (cols+2+strlen (tmp) >= DisplayWidth) cols = wrap ();
cols += uprintf (" v%s", tmp);
}
if (json_getValue (object, "isNode", tmp, 5)
&& strcmp (tmp, "true") == 0)
{
if ((cols + 5) >= DisplayWidth) cols = wrap ();
cols += uprintf (" NODE");
}
if (json_getValue (object, "isBBS", tmp, 5)
&& strcmp (tmp, "true") == 0)
{
if ((cols + 4) >= DisplayWidth) cols= wrap ();
cols += uprintf (" BBS");
}
if (json_getValue (object, "isPMS", tmp, 5)
&& strcmp (tmp, "true") == 0)
{
if ((cols + 4) >= DisplayWidth) cols= wrap ();
cols += uprintf (" PMS");
}
if (json_getValue (object, "isXRChat", tmp, 5)
&& strcmp (tmp, "true") == 0)
{
if ((cols + 7) >= DisplayWidth) cols = wrap ();
cols += uprintf (" XRCHAT");
}
if (json_getValue (object, "isRTChat", tmp, 5)
&& strcmp (tmp, "true") == 0)
{
if ((cols + 7) >= DisplayWidth) cols = wrap ();
cols += uprintf (" RTCHAT");
}
if (json_getValue (object, "isRMS", tmp, 5)
&& strcmp (tmp, "true"))
{
if ((cols + 4) >= DisplayWidth) cols = wrap ();
cols += uprintf (" RMS");
}
if (json_getValue (object, "isDXClUS", tmp, 5)
&& strcmp (tmp, "true") == 0)
{
if ((cols + 7) >= DisplayWidth) cols= wrap ();
cols += uprintf (" DXCLUS");
}
if (json_getValue (object, "timestamp", tmp, 40))
{
// There are two typs of timestamps currently in use...
if (strchr (tmp, 'T')) // It's ISO-8601
{
// 2025-10-24T12:46:52Z
if ((cols + 21) >= DisplayWidth) cols= wrap ();
cols += uprintf (" %s", tmp);
}
else // It's Unix time
{
time_t t = atoi (tmp);
if (((unsigned)t) > 18000)
{
struct tm *tim = localtime (&t);
if ((cols + 12) >= DisplayWidth) cols= wrap ();
cols += uprintf (" %02d/%02d %02d:%02d",
tim->tm_mday, tim->tm_mon+1,
tim->tm_hour, tim->tm_min);
}
}
}
if (json_getValue (object, "tzMins", tmp, 8))
{
if (cols+3+strlen (tmp) >= DisplayWidth) cols = wrap ();
uprintf (" tz=%s", tmp);
}
}
}
/**********************************************************************/
/* Purpose: Decode and display ARP headers
* Called by: process_json() for ptcl="ARP"
* Arguments: Pointer to string containing serialised JSON object.
* Returns: None
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
static void trace_arp (const char *json)
{
char tmp [80];
if ((TraceFlags & TRACE_ARP) == 0) return;
// Older software doesn't include these fields
if (json_getValue (json, "arpOp", tmp, 79) == NULL) return;
uprintf ("%sARP %s", Margin, tmp);
if (json_getValue (json, "arpHwType", tmp, 79))
uprintf (" hwtype=%s", tmp);
if (json_getValue (json, "arpHwLen", tmp, 79))
uprintf (" hwlen=%s", tmp);
if (json_getValue (json, "arpPtcl", tmp, 79))
uprintf (" prot=%s", tmp);
if (json_getValue (json, "arpSndAddr", tmp, 79))
uprintf ("%ssnd=%s", Margin, tmp);
if (json_getValue (json, "arpTgtAddr", tmp, 79))
uprintf (" tgt=%s", tmp);
if (json_getValue (json, "arpSndHw", tmp, 79))
uprintf (" snd_hw=%s", tmp);
if (json_getValue (json, "arpTgtHw", tmp, 79))
uprintf (" tgt_hw=%s", tmp);
}
/**********************************************************************/
/* Purpose: Decode and display IP headers
* Called by: process_json() for ptcl="IP"
* Arguments: Pointer to string containing serialised JSON object.
* Actions: Traces the main IP header fields, but not the payload
* Affects: stdout only
* Returns: None
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
static void trace_ip (const char *json)
{
char tmp [80], src [16], dst [16];
if ((TraceFlags & TRACE_IP) == 0) return;
// Older software doesn't include these fields
if (json_getValue (json, "ipFrom", src, 15) == NULL
|| json_getValue (json, "ipTo", dst, 15) == NULL)
return;
// IP: 44.136.16.50 > 44.136.16.52 iplen=28 ttl=127 id=ABA0 ptcl=1 ICMP
uprintf ("%sIP: %s > %s", Margin, src, dst);
if (json_getValue (json, "ipLen", tmp, 6)) uprintf (" iplen=%s", tmp);
if (json_getValue (json, "ipTTL", tmp, 3)) uprintf (" ttl=%s", tmp);
if (json_getValue (json, "ipID", tmp, 6)) uprintf (" id=%s", tmp);
if (json_getValue (json, "ipPtcl", tmp, 6)) uprintf (" ptcl=%s", tmp);
if (json_getValue (json, "ipProto", tmp, 8)) uprintf (" %s", tmp);
}
/**********************************************************************/
/* Purpose: Decode and display NetRom routing information frames
* Called by: trace_netrom() only.
* Arguments: Pointer to string containing serialised JSON object.
* Actions: Checks the value of "l3Type", vcalling the appropriate
* function according to that value.
* Returns: None
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
static void trace_netromRoutingInfo (const char *json)
{
char type [16];
if (json_getValue (json, "type", type, 15) == NULL)
{
if (TraceFlags & TRACE_WARNINGS)
uprintf (" [missing 'type']");
return;
}
if (strcmp (type, "NODES") == 0) trace_nodes (json);
else if (strcmp (type, "INP3") == 0) trace_inp3 (json);
else if (TraceFlags & TRACE_WARNINGS)
uprintf (" [unknown 'type' '%s'", type);
// Future types go here
}
/**********************************************************************/
/* Purpose: Trace a netrom routing poll
* Called by: x
* Arguments: Pointer to string containing serialised JSON object.
* Actions: x
* Affects: x
* Returns: x
* Notes: x
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
static void trace_netromRoutingPoll (const char *json)
{
/// TODO: Populate me
}
/**********************************************************************/
/* Purpose: Decode and display NetRom layer 4 segments
* Called by: trace_netromL3() only
* Arguments: Pointer to string containing serialised JSON object.
* Actions: For l4Type "PROT_EXT" this currently displays only the
* protocol type (if known), else the protocol family and
* protocol number (if protocol not known).
* If l4Type is not "PROT_EXT", the frame is either a L4
* transport frame, or a "Netrom Record Route" frame. Both
* types are traced. For L4 transport, only the headers are
* traced, the payloads are considered sensitive.
* Affects: stdout only.
* Returns: None
* Notes: Tracing of NCMP, NDP, GNET etc could be added if required
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
static void trace_netromL4 (const char *json)
{
char tmp [2048], l4type [16];
if ((TraceFlags & TRACE_L4) == 0) return;
// NetRom L4 Frame Type
if (json_getValue (json, "l4type", l4type, 15) == 0)
{
if (TraceFlags & TRACE_WARNINGS)
uprintf (" [missing l4type]\n");
return;
}
if (strcmp (l4type, "unknown") == 0)
{
if (TraceFlags & TRACE_WARNINGS)
uprintf (" [unknown l4type]\n");
return;
}
if (strcmp (l4type, "PROT EXT") == 0)
{
uprintf (" <%s>", l4type);
if (json_getValue (json, "l4Family", tmp, 80))
uprintf (" pf=%s", tmp);
if (json_getValue (json, "l4Proto", tmp, 80))
uprintf (" prot=%s", tmp);
return;
}
if (strcmp (l4type, "IP") == 0
|| strcmp (l4type, "NCMP") == 0
|| strcmp (l4type, "NDP") == 0
|| strcmp (l4type, "GNET") == 0)
{
/// TODO: Decode these properly one day
uprintf (" <%s>", l4type);
return;
}
if (strcmp (l4type, "NRR Request") == 0 // Netrom Record Route Request
|| strcmp (l4type, "NRR Reply") == 0) // Netrom Record Route Reply
{
uprintf (" <%s>", l4type);
if (json_getValue (json, "nrrId", tmp, 80))
uprintf (" id=%s", tmp);
if (json_getValue (json, "nrrRoute", tmp, 2047))
uprintf ("%sRoute: %s", Margin, tmp);
return;
}
if (json_getValue (json, "toCct", tmp, 8))
uprintf (" cct=%s", tmp);
if (strcmp (l4type, "CONN REQ") == 0
|| strcmp (l4type, "CONN_REQX") == 0)
{
uprintf (" <%s>", l4type);
if (json_getValue (json, "window", tmp, 8))
uprintf (" w=%s", tmp);
if (json_getValue (json, "srcUser", tmp, 9))
uprintf ("\n %s", tmp);
else return;
if (json_getValue (json, "srcNode", tmp, 9))
uprintf (" at %s", tmp);
if (json_getValue (json, "service", tmp, 8))
uprintf (" svc=%s", tmp);
if (json_getValue (json, "l4t1", tmp, 8))
uprintf (" t/o=%s", tmp);
if (json_getValue (json, "bpqSpy", tmp, 8))
uprintf (" bpqSpy=%s", tmp);
return;
}
if (strcmp (l4type, "CONN ACK") == 0)
{
uprintf (" <%s>", l4type);
if (json_getValue (json, "window", tmp, 8))
uprintf (" w=%s", tmp);
if (json_getValue (json, "fromCct", tmp, 8))
uprintf (" myCct=%s", tmp);
return; // ??
}
if (strcmp (l4type, "CONN NAK") == 0)
{
uprintf (" <%s>", l4type);
return; // ??
}
if (strcmp (l4type, "DREQ") == 0
|| strcmp (l4type, "DACK") == 0)
{
uprintf (" <%s>", l4type);
return;
}
if (strcmp (l4type, "RSET") == 0)
{
uprintf (" <%s>", l4type);
if (json_getValue (json, "fromCct", tmp, 8))
uprintf (" myCct=%s", tmp);
return;
}
if (strcmp (l4type, "INFO") == 0)
{
uprintf (" <%s", l4type);
if (json_getValue (json, "txSeq", tmp, 8))
uprintf (" S%s", tmp);
if (json_getValue (json, "rxSeq", tmp, 8))
uprintf (" R%s", tmp);
uprintf (">");
if (json_getValue (json, "paylen", tmp, 8))
uprintf (" ilen=%s", tmp);
if (json_getValue (json, "payload", tmp, 2047))
uprintf (":%s%s", Margin, tmp);
}
else if (strcmp (l4type, "INFO ACK") == 0)
{
uprintf (" <%s", l4type);
if (json_getValue (json, "rxSeq", tmp, 8))
uprintf (" R%s", tmp);
uprintf (">");
}
if (json_getValue (json, "chokeFlag", tmp, 8))
uprintf (" <CHOKE>");
if (json_getValue (json, "nakFlag", tmp, 8))
uprintf (" <NAK>");
if (json_getValue (json, "moreFlag", tmp, 8))
uprintf (" <MORE>");
}
/**********************************************************************/
/* Purpose: Trace L3RTT frames
* Called by: trace_netromL3() if l3Dest is "L3RTT"
* Arguments: Pointer to serialised JSON object.
* Notes: L3RTT is a "retrofit" to NetRom. It includes an L4 header
* which makes it look like an L4 INFO frame with circuit
* number, send and receive sequence numbers all zero. But
* it most definitely belongs in layer 3.
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
static void trace_l3rtt (const char *json)
{
char tmp [512];
/* "l4type" should be "INFO", "toCct", "txSeq" and "rxSeq" should all
* be 0, if you want to bother to check them
* */
if (json_getValue (json, "paylen", tmp, 8))
uprintf (" ilen=%s", tmp);
if ((TraceFlags & TRACE_L3RTT) == 0) return;
// Payload chan be up to 236 chara, so it will wrap untidily
/// TODO: parse the payload & present the fields in a neater form
if (json_getValue (json, "payload", tmp, 511))
uprintf (":%s%s", Margin, tmp);
}
/**********************************************************************/
/* Purpose: Display the L3 routing header, then trace layer 4
* Called by: trace_netrom() if l3Type is "netrom".
* Arguments: Pointer to string containing serialised JSON object.
* Actions: Displays the L3 source and dest calls, and TTL, then
* traces layer 4.
* Affects: stdout only
* Returns: None
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
static void trace_netromL3 (const char *json)
{
char tmp [16];
bool isL3RTT;
if (json_getValue (json, "l3src", tmp, 10))
uprintf ("%sNTRM: %s", Margin, tmp); // Layer 3 source
if (json_getValue (json, "l3dst", tmp, 10))
uprintf (" to %s", tmp); // layer 3 dest
isL3RTT = (strcmp (tmp, "L3RTT") == 0);
if (json_getValue (json, "ttl", tmp, 8))
uprintf (" ttl=%s", tmp); // Layer 3 time to live
if (isL3RTT) trace_l3rtt (json);
else trace_netromL4 (json);
}
/**********************************************************************/
/* Purpose: Trace NetRom (PID 0xCF) frames
* Called by: process_json() only.
* Arguments: Pointer to string containing serialised JSON object.
* Actions: Calls the appropriate decoder based on l3Type
* Affects: stdout only
* Returns: None
* Created: 24/10/2025 by Paula Dowie G8PZT.*/
/**********************************************************************/
static void trace_netrom (const char *json)
{
char tmp [80];
if ((TraceFlags & TRACE_NETROM) == 0) return;
if (json_getValue (json, "l3Type", tmp, 79) == 0)
{
if (TraceFlags & TRACE_WARNINGS)
uprintf (" [missing 'l3Type']");
return;
}
if (strcmp (tmp, "NetRom") == 0) trace_netromL3 (json);
else if (strcmp (tmp, "Routing info") == 0)
trace_netromRoutingInfo (json);
else if (strcmp (tmp, "Routing poll") == 0)
trace_netromRoutingPoll (json);
else if (TraceFlags & TRACE_WARNINGS)
uprintf (" [unknown 'l3type': '%s'", tmp);
}
/**********************************************************************/
/* Purpose: Process a serialised JSON object.
* Called by: main() only.
* Arguments: Pointer to string containing serialised JSON object.
* Actions: Extracts values from the JSON string, applies filters,
* sets trace colours, traces AX25 layer2 frame and
* optionally into the layers above.
* Affects: stdout only.
* Returns: None
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
static void process_json (const char *json)
{
char tmp [1024], reporter [16], portnum [16], src [16], dst [16];
char l2type [8], dirn [8], isRF [8], ptcl [8];
if (json_getValue (json, "@type", tmp, 80) == 0)
{
if (TraceFlags & TRACE_WARNINGS)
printf ("[missing '@type']\n");
return;
}
/// TODO: Test for and process other report types here if desired
if (strcmp (tmp, "L2Trace") != 0) return;
// Extract some mandatory fields
if (json_getValue (json, "reportFrom", reporter, 15) == NULL
|| json_getValue (json, "port", portnum, 15) == NULL
|| json_getValue (json, "srce", src, 15) == NULL
|| json_getValue (json, "dest", dst, 15) == NULL
|| json_getValue (json, "l2Type", l2type, 7) == NULL)
{
if (TraceFlags & TRACE_WARNINGS)
printf ("[Mandatory field missing]\n");
return;
}
// Extract some of the optional values.
if (json_getValue (json, "dirn", dirn, 4) == NULL) *dirn = 0;
if (json_getValue (json, "isRF", isRF, 4) == NULL) *isRF = 0;
if (json_getValue (json, "ptcl", ptcl, 7) == NULL) *ptcl = 0;
if (strcmp (l2type, "UI") == 0
&& (TraceFlags & TRACE_UI) == 0)
return; // UI Not wanted
// Filter by reporting node
if (*ReportFilter // If report filter is enabled
&& strcasecmp (reporter, ReportFilter) != 0) // and no match
return; // Ignore this packet
// Filter by node's port number
if (PortFilter && atoi (portnum) != PortFilter) return;
// Filter by packet type
if (*TypeFilter
&& strcasecmp (l2type, TypeFilter) != 0)
return;
// Filter by AX25 source call
if (*SrcFilter
&& strcasecmp (src, SrcFilter) != 0)
return;
// Filter by AX25 source and destination calls
if (*DstFilter
&& strcasecmp (dst, DstFilter) != 0)
return;
// Filter by either AX25 source or destination call
if (*AllFilter
&& strcasecmp (dst, AllFilter) != 0
&& strcasecmp (src, AllFilter) != 0)
return;
// Filter by protocol ID
if (*ProtoFilter
&& (*ptcl == 0 || strcasecmp (ptcl, ProtoFilter)) != 0)
return;
if (TraceFlags & TRACE_COLOR)
{
const char *colorstr;
if (*isRF == 't') // True
{
switch (*dirn)
{
case 's': colorstr = "\x1b[91m"; break; // red
case 'r': colorstr = "\x1b[92m"; break; // green
default: colorstr = "\x1b[93m"; break; // yellow
}
}
else if (*isRF == 'f') // False
{
switch (*dirn)
{
case 's': colorstr = "\x1b[38;2;255;150;150m"; break;
case 'r': colorstr = "\x1b[38;2;50;255;150m"; break; // Cyan
default: colorstr = "\x1b[94m"; break; // Blue
}
}
else // Unknown RF/Inet status
colorstr = "\x1b[0m"; // white
/* Sending colour information to capture file allows it to be
* played back in colour but makes it difficult to read with a
* text editor. Therefore it is turned off by default.
* */
if (TraceFlags & TRACE_COLOR2FILE) uprintf ("%s", colorstr);
else printf ("%s", colorstr);
}
// If raw JSON wanted, print it before the trace (defaults off)
if (TraceFlags & TRACE_JSON) uprintf ("%s\n", json);
// Print a blank line between traces (dedaults on)
if (TraceFlags & TRACE_LBRK) uprintf ("\n");
// If timestamp is wanted (defaults on)
if (TraceFlags & TRACE_STAMP)
{
struct tm *tp;
time_t t;
if (json_getValue (json, "time", tmp, 20)) t = atoi (tmp);
else t = time (NULL);
tp = gmtime (&t);
uprintf ("%02d:%02d:%02d ",
tp->tm_hour, tp->tm_min, tp->tm_sec);
}
if (TraceFlags & TRACE_HDRLIN)
{
// Metadata and trace on separate lines for clarity
uprintf ("%s port %s", reporter, portnum);
if (*isRF) uprintf (*isRF == 't' ? " (RF)" : " (Non-RF)");
if (*dirn) uprintf (" %s", dirn);
uprintf (":\n ");
}
else // Metadata and trace on one messy line
{
sprintf (tmp, "%s(%s)%c",
reporter, portnum, *dirn ? toupper (*dirn) : ' ');
uprintf ("%s ", tmp);
}
// Display L2 source, destination and type
uprintf ("%s>%s <%s", src, dst, l2type);
// The format of these varies with frame type...
if (json_getValue (json, "cr", tmp, 2)) uprintf (" %s", tmp);
if (json_getValue (json, "pf", tmp, 2)) uprintf (" %s", tmp);
if (json_getValue (json, "rseq", tmp, 3)) uprintf (" R%s", tmp);
if (json_getValue (json, "tseq", tmp, 3)) uprintf (" S%s", tmp);
uprintf (">");
// Display info field length and pid if present
if (json_getValue (json, "ilen", tmp, 10)) uprintf (" ilen=%s", tmp);
if (json_getValue (json, "pid", tmp, 10)) uprintf (" pid=%s", tmp);
if (*ptcl) uprintf (" %s", ptcl);
// Decode some payloads
if (*ptcl)
{
if (strcmp (ptcl, "NET/ROM") == 0) trace_netrom (json);
else if (strcmp (ptcl, "DATA") == 0)
{
// The "info" field is present only for "UI" frames
if (json_getValue (json, "info", tmp, 1023))
uprintf (":%s%s", Margin, tmp);
// The "icrc" field is present only for "I" frames
else if (json_getValue (json, "icrc", tmp, 8))
uprintf (" CRC=%s", tmp);
}
else if (strcmp (ptcl, "IP") == 0) trace_ip (json);
else if (strcmp (ptcl, "ARP") == 0) trace_arp (json);
/// TODO: Add flexnet
}
uprintf ("\n");
}
/**********************************************************************/
/* Purpose: Display program help.
* Called by: main() if "-h" switch is found.
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
static void showHelp (void)
{
printf ("Usage: pmnptrace [options]\n\n");
printf ("Options:\n\n"
" -3 Don't trace NetRom layer 3 or above\n"
" -4 Don't trace NetRom layer 4 or above\n"
" -a <callsign> Show ALL frames to or from <callsign>\n"
" -c Don't colourise the traces\n"
" -C Include colour information in capture file\n"
" -f <callsign> Show only frames addressed FROM <callsign>\n"
" -h Show this message and exit\n"
" -H Show header on separate line to trace\n"
" -i Don't trace contents of INP3 routing unicasts\n"
" -j Show the raw JSON before each trace\n"
" -k Don't show L3RTT info field\n"
" -l Suppress blank line between traces\n"
" -n Don't trace contents of NetRom nodes broadcasts\n"
" -o <file> Output trace to <file>\n"
" -p <portnum> Show reports only from <portnum>\n"
" -P <protocol> Show only frames with this L3 protocol\n"
" -q No display when capturing to file (quiet)\n"
" -r <callsign> Show reports only from <callsign>\n"
" -s Suppress time stamp\n"
" -t <callsign> Show only frames addressed TO <callsign>\n"
" -T <frametype> Show only this AX25 frametype, e.g. \"-T UI\"\n"
" -u Don't display UI frames\n"
" -w <width> Display width (default 80 cols)\n"
" -W Enable warnings of missing/bad JSON fields\n\n");
}
/**********************************************************************/
/* Purpose: Main function
* Called by: From command line
* Arguments: Program name, plus zero or more options
* Actions: Sets filters according to argument list, then loops to
* assemble un-named JSON objects from stdin, dispatching
* completed objects to the process_json() function.
* Returns: 0 upon normal exit, else -1
* Notes: x
* Created: 24/10/2025 by Paula Dowie G8PZT.
* Modified: */
/**********************************************************************/
int main (int argc, char *argv[])
{
char buffer [4096], *cp;
int braceLevel = 0;
int c, ch, escaped = 0, inString=0;
uprintf ("\n\"pnmptrace\" JSON to AX25 Trace Decoder for PNMP\n");
uprintf ("Version %s, Copyright (C) 2025 G8PZT\n\n", VERSION);
if (argc < 2) uprintf ("Use 'pnmptrace -h' to display help, "
"Ctrl-C exits\n\n", argv [0]);
while (1)
{
if ((c = getopt (argc, argv, "34a:cijklnqsuhHWf:o:p:r:t:P:T:w:")) < 0)
break; // End of options
switch (c)
{
case 'h': showHelp (); return (0);
case 'c': TraceFlags &= ~TRACE_COLOR; break;
case 'C': TraceFlags |= TRACE_COLOR2FILE; break;
case 'u': TraceFlags &= ~TRACE_UI; break;
case 'i': TraceFlags &= ~TRACE_INP3; break;
case 'n': TraceFlags &= ~TRACE_NODES; break;
case '3': TraceFlags &= ~TRACE_NETROM; break;
case '4': TraceFlags &= ~TRACE_L4; break;
case 's': TraceFlags &= ~TRACE_STAMP; break;
case 'k': TraceFlags &= ~TRACE_L3RTT; break;
case 'l': TraceFlags &= ~TRACE_LBRK; break;
case 'j': TraceFlags |= TRACE_JSON; break;
case 'H': TraceFlags |= TRACE_HDRLIN; break;
case 'a': strncpy (AllFilter, optarg, 15); break;
case 'f': strncpy (SrcFilter, optarg, 15); break;
case 't': strncpy (DstFilter, optarg, 15); break;
case 'T': strncpy (TypeFilter, optarg, 15); break;
case 'r': strncpy (ReportFilter, optarg, 15); break;
case 'o': strncpy (CaptureFile, optarg, 255); break;
case 'p': PortFilter = atoi (optarg); break;
case 'P': strncpy (ProtoFilter, optarg, 15); break;
case 'q': TraceFlags |= TRACE_QUIET; break;
case 'w': DisplayWidth = atoi (optarg); break;
case 'W': TraceFlags |= TRACE_WARNINGS; break;
}
}
if (*CaptureFile)
{
if ((FpCapture = fopen (CaptureFile, "w")) == NULL)
{
printf ("Can't open capture file '%s'\n", CaptureFile);
return (-1);
}
printf ("Capturing traces to file '%s'\n", CaptureFile);
}
if (*ReportFilter)
uprintf ("Showing reports from node '%s' only\n", ReportFilter);
if (PortFilter)
uprintf ("Showing frames to/from port (%d) only\n",
PortFilter);
if (*SrcFilter)
uprintf ("Showing frames with L2 source call '%s' only\n",
SrcFilter);
if (*DstFilter)
uprintf ("Showing frames with L2 destination call '%s' only\n",
DstFilter);
if (*AllFilter)
uprintf ("Showing frames to/from L2 call '%s' only\n", AllFilter);
if (*TypeFilter)
uprintf ("Showing '%s' frames only\n", TypeFilter);
if (*ProtoFilter)
uprintf ("Showing frames with L3 protocol '%s' only\n",
ProtoFilter);
if ((TraceFlags & TRACE_UI) == 0) uprintf ("Not showing UI frames\n");
if ((TraceFlags & TRACE_NETROM) == 0)
uprintf ("Not decoding NODES broadcasts\n");
if ((TraceFlags & TRACE_INP3) == 0)
uprintf ("Not decoding INP3 unicasts\n");
if ((TraceFlags & TRACE_NETROM) == 0)
uprintf ("Not decoding NetRom Layer 3 or above\n");
if ((TraceFlags & TRACE_L4) == 0)
uprintf ("Not decoding NetRom Layer 4 or above\n");
if ((TraceFlags & TRACE_L3RTT) == 0)
uprintf ("Not showing L3RTT frame contents\n");
if (TraceFlags & TRACE_JSON) uprintf ("Including JSON data\n");
if ((TraceFlags & TRACE_STAMP) == 0)
uprintf ("Time stamp disabled\n");
cp = buffer;
while (1) // Forever loop
{
// Get a char from stdin - blocking
if ((ch = getchar ()) == EOF) break;
if (braceLevel == 0) // Waiting for opening brace
{
if (ch == '{') // Found the opening brace
{
braceLevel = 1;
cp = buffer; // Point cp at start of object buffer
}
continue;
}
// If we get here, braceLevel is > 0
if (ch == '}') // Possible end of object
{
if (!escaped // If not in "escaped" mode,
&& !inString // and not within a string,
&& --braceLevel == 0) // and it is the final brace
{
*cp++ = 0; // Terminate the string
process_json (buffer); // Process the object
cp = buffer; // Reset the pointer
continue;
}
}
*cp++ = ch; // Copy the character to the object buffer
if (ch == '{' // Possible start of object within object
&& !escaped && !inString)
{
braceLevel++;
continue;
}
if (escaped)
{
escaped = 0;
continue;
}
else // Not in escaped mode
{
if (ch == '\\') // If it's the escape character
{
escaped = 1; // Set escaped mode
continue;
}
}
// Not escape and not '\'
if (ch == '"') // Start of end of string
{
if (inString) inString = 0;
else inString = 1;
continue;
}
} // end of while()
if (FpCapture) fclose (FpCapture);
return (0);
}
