diff --git a/CHANGES.md b/CHANGES.md index f979b54..ba28d1d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,9 @@ ### New Features: ### +- Additional documentation location to slow down growth of main repository. [https://github.com/wb2osz/direwolf-doc](https://github.com/wb2osz/direwolf-doc) + +- New ICHANNEL configuration option to map a KISS client application channel to APRS-IS. Packets from APRS-IS will be presented to client applications as the specified channel. Packets sent, by client applications, to that channel will go to APRS-IS rather than a radio channel. Details in ***Internal-Packet-Routing.pdf***. - New variable speed option for gen_packets. For example, "-v 5,0.1" would generate packets from 5% too slow to 5% too fast with increments of 0.1. Some implementations might have imprecise timing. Use this to test how well TNCs tolerate sloppy timing. diff --git a/src/beacon.c b/src/beacon.c index 4c78198..69a7270 100644 --- a/src/beacon.c +++ b/src/beacon.c @@ -162,6 +162,7 @@ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct int chan = g_misc_config_p->beacon[j].sendto_chan; if (chan < 0) chan = 0; /* For IGate, use channel 0 call. */ + if (chan >= MAX_CHANS) chan = 0; // For ICHANNEL, use channel 0 call. if (g_modem_config_p->chan_medium[chan] == MEDIUM_RADIO || g_modem_config_p->chan_medium[chan] == MEDIUM_NETTNC) { @@ -621,6 +622,7 @@ static void * beacon_thread (void *arg) // On reboot, the time is in the past. // After time gets set from GPS, all beacons from that interval are sent. // FIXME: This will surely break time slotted scheduling. + // TODO: The correct fix will be using monotonic, rather than clock, time. /* craigerl: if next beacon is scheduled in the past, then set next beacon relative to now (happens when NTP pushes clock AHEAD) */ /* fixme: if NTP sets clock BACK an hour, this thread will sleep for that hour */ @@ -805,11 +807,17 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo) assert (bp->sendto_chan >= 0); - strlcpy (mycall, g_modem_config_p->achan[bp->sendto_chan].mycall, sizeof(mycall)); + if (g_modem_config_p->chan_medium[bp->sendto_chan] == MEDIUM_IGATE) { // ICHANNEL uses chan 0 mycall. + // TODO: Maybe it should be allowed to have own. + strlcpy (mycall, g_modem_config_p->achan[0].mycall, sizeof(mycall)); + } + else { + strlcpy (mycall, g_modem_config_p->achan[bp->sendto_chan].mycall, sizeof(mycall)); + } if (strlen(mycall) == 0 || strcmp(mycall, "NOCALL") == 0) { text_color_set(DW_COLOR_ERROR); - dw_printf ("MYCALL not set for beacon in config file line %d.\n", bp->lineno); + dw_printf ("MYCALL not set for beacon to chan %d in config file line %d.\n", bp->sendto_chan, bp->lineno); return; } @@ -1046,7 +1054,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo) text_color_set(DW_COLOR_XMIT); dw_printf ("[ig] %s\n", beacon_text); - igate_send_rec_packet (0, pp); + igate_send_rec_packet (-1, pp); // Channel -1 to avoid RF>IS filtering. ax25_delete (pp); break; diff --git a/src/config.c b/src/config.c index 5a8eead..492fa22 100644 --- a/src/config.c +++ b/src/config.c @@ -5594,7 +5594,8 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ } else if (value[0] == 'r' || value[0] == 'R') { int n = atoi(value+1); - if ( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) { + if (( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) + && p_audio_config->chan_medium[n] != MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Simulated receive on channel %d is not valid.\n", line, n); continue; @@ -5604,7 +5605,8 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ } else if (value[0] == 't' || value[0] == 'T' || value[0] == 'x' || value[0] == 'X') { int n = atoi(value+1); - if ( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) { + if (( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) + && p_audio_config->chan_medium[n] != MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n); continue; @@ -5615,7 +5617,8 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ } else { int n = atoi(value); - if ( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) { + if (( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) + && p_audio_config->chan_medium[n] != MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n); continue; @@ -5864,19 +5867,32 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ if (b->sendto_type == SENDTO_XMIT) { - if ( b->sendto_chan < 0 || b->sendto_chan >= MAX_CHANS || p_audio_config->chan_medium[b->sendto_chan] == MEDIUM_NONE) { + if (( b->sendto_chan < 0 || b->sendto_chan >= MAX_CHANS || p_audio_config->chan_medium[b->sendto_chan] == MEDIUM_NONE) + && p_audio_config->chan_medium[b->sendto_chan] != MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, b->sendto_chan); return (0); } - if ( strcmp(p_audio_config->achan[b->sendto_chan].mycall, "") == 0 || - strcmp(p_audio_config->achan[b->sendto_chan].mycall, "NOCALL") == 0 || - strcmp(p_audio_config->achan[b->sendto_chan].mycall, "N0CALL") == 0 ) { + if (p_audio_config->chan_medium[b->sendto_chan] == MEDIUM_IGATE) { // Prevent subscript out of bounds. + // Will be using call from chan 0 later. + if ( strcmp(p_audio_config->achan[0].mycall, "") == 0 || + strcmp(p_audio_config->achan[0].mycall, "NOCALL") == 0 || + strcmp(p_audio_config->achan[0].mycall, "N0CALL") == 0 ) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", b->sendto_chan); - return (0); + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", 0); + return (0); + } + } else { + if ( strcmp(p_audio_config->achan[b->sendto_chan].mycall, "") == 0 || + strcmp(p_audio_config->achan[b->sendto_chan].mycall, "NOCALL") == 0 || + strcmp(p_audio_config->achan[b->sendto_chan].mycall, "N0CALL") == 0 ) { + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", b->sendto_chan); + return (0); + } } } diff --git a/src/dtime_now.c b/src/dtime_now.c index af7b98a..e5b40c6 100644 --- a/src/dtime_now.c +++ b/src/dtime_now.c @@ -25,9 +25,9 @@ /*------------------------------------------------------------------ * - * Name: dtime_now + * Name: dtime_realtime * - * Purpose: Return current time as double precision. + * Purpose: Return current wall clock time as double precision. * * Input: none * @@ -40,11 +40,24 @@ * part of a second, and having extra calculations everywhere, * simply use double precision floating point to make usage * easier. - * + * + * NOTE: This is not a good way to calculate elapsed time because + * it can jump forward or backware via NTP or other manual setting. + * + * Use the monotonic version for measuring elapsed time. + * + * History: Originally I called this dtime_now. We ran into issues where + * we really cared about elapsed time, rather than wall clock time. + * The wall clock time could be wrong at start up time if there + * is no realtime clock or Internet access. It can then jump + * when GPS time or Internet access becomes available. + * All instances of dtime_now should be replaced by dtime_realtime + * if we want wall clock time, or dtime_monotonic if it is to be + * used for measuring elapsed time, such as between becons. + * *---------------------------------------------------------------*/ - -double dtime_now (void) +double dtime_realtime (void) { double result; @@ -55,7 +68,7 @@ double dtime_now (void) GetSystemTimeAsFileTime (&ft); - result = ((( (double)ft.dwHighDateTime * (256. * 256. * 256. * 256.) + + result = ((( (double)ft.dwHighDateTime * (256. * 256. * 256. * 256.) + (double)ft.dwLowDateTime ) / 10000000.) - 11644473600.); #else /* tv_sec is seconds from Jan 1, 1970. */ @@ -63,6 +76,10 @@ double dtime_now (void) struct timespec ts; #ifdef __APPLE__ + +// Why didn't I use clock_gettime? +// Not available before Max OSX 10.12? https://github.com/gambit/gambit/issues/293 + struct timeval tp; gettimeofday(&tp, NULL); ts.tv_nsec = tp.tv_usec * 1000; @@ -75,6 +92,83 @@ double dtime_now (void) #endif +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("dtime_realtime() returns %.3f\n", result ); +#endif + + return (result); +} + + +/*------------------------------------------------------------------ + * + * Name: dtime_monotonic + * + * Purpose: Return montonically increasing time, which is not influenced + * by the wall clock changing. e.g. leap seconds, NTP adjustments. + * + * Input: none + * + * Returns: Time as double precision, so we can get resolution + * finer than one second. + * + * Description: Use this when calculating elapsed time. + * + *---------------------------------------------------------------*/ + +double dtime_monotonic (void) +{ + double result; + +#if __WIN32__ + +// FIXME: +// This is still returning wall clock time. +// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount64 +// GetTickCount64 would be ideal but it requires Vista or Server 2008. +// As far as I know, the current version of direwolf still works on XP. +// +// As a work-around, GetTickCount could be used if we add extra code to deal +// with the wrap around after about 49.7 days. +// Resolution is only about 10 or 16 milliseconds. Is that good enough? + + /* 64 bit integer is number of 100 nanosecond intervals from Jan 1, 1601. */ + + FILETIME ft; + + GetSystemTimeAsFileTime (&ft); + + result = ((( (double)ft.dwHighDateTime * (256. * 256. * 256. * 256.) + + (double)ft.dwLowDateTime ) / 10000000.) - 11644473600.); +#else + /* tv_sec is seconds from Jan 1, 1970. */ + + struct timespec ts; + +#ifdef __APPLE__ + +// FIXME: Does MacOS have a monotonically increasing time? +// https://stackoverflow.com/questions/41509505/clock-gettime-on-macos + + struct timeval tp; + gettimeofday(&tp, NULL); + ts.tv_nsec = tp.tv_usec * 1000; + ts.tv_sec = tp.tv_sec; +#else + +// This is the only case handled properly. +// Probably the only one that matters. +// It is common to have a Raspberry Pi, without Internet, +// starting up direwolf before GPS/NTP adjusts the time. + + clock_gettime (CLOCK_MONOTONIC, &ts); +#endif + + result = ((double)(ts.tv_sec) + (double)(ts.tv_nsec) * 0.000000001); + +#endif + #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dtime_now() returns %.3f\n", result ); @@ -84,6 +178,7 @@ double dtime_now (void) } + /*------------------------------------------------------------------ * * Name: timestamp_now @@ -104,7 +199,7 @@ double dtime_now (void) void timestamp_now (char *result, int result_size, int show_ms) { - double now = dtime_now(); + double now = dtime_realtime(); time_t t = (int)now; struct tm tm; @@ -150,7 +245,7 @@ void timestamp_now (char *result, int result_size, int show_ms) void timestamp_user_format (char *result, int result_size, char *user_format) { - double now = dtime_now(); + double now = dtime_realtime(); time_t t = (int)now; struct tm tm; @@ -191,7 +286,7 @@ void timestamp_user_format (char *result, int result_size, char *user_format) void timestamp_filename (char *result, int result_size) { - double now = dtime_now(); + double now = dtime_realtime(); time_t t = (int)now; struct tm tm; diff --git a/src/dtime_now.h b/src/dtime_now.h index ac22f7d..411534b 100644 --- a/src/dtime_now.h +++ b/src/dtime_now.h @@ -1,9 +1,18 @@ -extern double dtime_now (void); +extern double dtime_realtime (void); + +extern double dtime_monotonic (void); + void timestamp_now (char *result, int result_size, int show_ms); void timestamp_user_format (char *result, int result_size, char *user_format); -void timestamp_filename (char *result, int result_size); \ No newline at end of file +void timestamp_filename (char *result, int result_size); + + +// FIXME: remove temp workaround. +// Needs many scattered updates. + +#define dtime_now dtime_realtime diff --git a/src/igate.c b/src/igate.c index 719bef4..73b0bbd 100644 --- a/src/igate.c +++ b/src/igate.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013, 2014, 2015, 2016 John Langner, WB2OSZ +// Copyright (C) 2013, 2014, 2015, 2016, 2023 John Langner, WB2OSZ // // 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 @@ -855,6 +855,9 @@ static void * connnect_thread (void *arg) * Purpose: Send a packet to the IGate server * * Inputs: chan - Radio channel it was received on. + * This is required for the RF>IS filtering. + * Beaconing (sendto=ig, chan=-1) and a client app sending + * to ICHANNEL should bypass the filtering. * * recv_pp - Pointer to packet object. * *** CALLER IS RESPONSIBLE FOR DELETING IT! ** @@ -902,7 +905,12 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) * In that case, the payload will have TCPIP in the path and it will be dropped. */ - if (save_digi_config_p->filter_str[chan][MAX_CHANS] != NULL) { +// Apply RF>IS filtering only if it same from a radio channel. +// Beacon will be channel -1. +// Client app to ICHANNEL is outside of radio channel range. + + if (chan >= 0 && chan < MAX_CHANS && // in radio channel range + save_digi_config_p->filter_str[chan][MAX_CHANS] != NULL) { if (pfilter(chan, MAX_CHANS, save_digi_config_p->filter_str[chan][MAX_CHANS], recv_pp, 1) != 1) { @@ -1517,32 +1525,36 @@ static void * igate_recv_thread (void *arg) int ichan = save_audio_config_p->igate_vchannel; - // Try to parse it into a packet object. - // This will contain "q constructs" and we might see an address - // with two alphnumeric characters in the SSID so we must use - // the non-strict parsing. + // My original poorly thoughtout idea was to parse it into a packet object, + // using the non-strict option, and send to the client app. + // + // A lot of things can go wrong with that approach. - // Possible problem: Up to 8 digipeaters are allowed in radio format. - // There is a potential of finding a larger number here. + // (1) Up to 8 digipeaters are allowed in radio format. + // There is a potential of finding a larger number here. + // + // (2) The via path can have names that are not valid in the radio format. + // e.g. qAC, T2HAKATA, N5JXS-F1. + // Non-strict parsing would force uppercase, truncate names too long, + // and drop unacceptable SSIDs. + // + // (3) The source address could be invalid for the RF address format. + // e.g. WHO-IS>APJIW4,TCPIP*,qAC,AE5PL-JF::ZL1JSH-9 :Charles Beadfield/New Zealand{583 + // That is essential information that we absolutely need to preserve. + // + // I think the only correct solution is to apply a third party header + // wrapper so the original contents are preserved. This will be a little + // more work for the application developer. Search for ":}" and use only + // the part after that. At this point, I don't see any value in encoding + // information in the source/destination so I will just use "X>X:}" as a prefix - packet_t pp3 = ax25_from_text((char*)message, 0); // 0 means not strict + char stemp[AX25_MAX_INFO_LEN]; + strlcpy (stemp, "X>X:}", sizeof(stemp)); + strlcat (stemp, (char*)message, sizeof(stemp)); + + packet_t pp3 = ax25_from_text(stemp, 0); // 0 means not strict if (pp3 != NULL) { - // Should we remove the VIA path? - - // For example, we might get something like this from the server. - // Lower case 'q' and non-numeric SSID are not valid for AX.25 over the air. - // K1USN-1>APWW10,TCPIP*,qAC,N5JXS-F1:T#479,100,048,002,500,000,10000000 - - // Should we try to retain all information and pass that along, to the best of our ability, - // to the client app, or should we remove the via path so it looks like this? - // K1USN-1>APWW10:T#479,100,048,002,500,000,10000000 - - // For now, keep it intact and see if it causes problems. Easy to remove like this: - // while (ax25_get_num_repeaters(pp3) > 0) { - // ax25_remove_addr (pp3, AX25_REPEATER_1); - // } - alevel_t alevel; memset (&alevel, 0, sizeof(alevel)); alevel.mark = -2; // FIXME: Do we want some other special case? @@ -1831,8 +1843,19 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan) * If we recently transmitted a 'message' from some station, * send the position of the message sender when it comes along later. * + * Some refer to this as a courtesy posit report but I don't + * think that is an official term. + * * If we have a position report, look up the sender and see if we should * bypass the normal filtering. + * + * Reference: https://www.aprs-is.net/IGating.aspx + * + * "Passing all message packets also includes passing the sending station's position + * along with the message. When APRS-IS was small, we did this using historical position + * packets. This has become problematic as it introduces historical data on to RF. + * The IGate should note the station(s) it has gated messages to RF for and pass + * the next position packet seen for that station(s) to RF." */ // TODO: Not quite this simple. Should have a function to check for position. diff --git a/src/kiss_frame.c b/src/kiss_frame.c index 862fb71..e304a83 100644 --- a/src/kiss_frame.c +++ b/src/kiss_frame.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013, 2014, 2017 John Langner, WB2OSZ +// Copyright (C) 2013, 2014, 2017, 2023 John Langner, WB2OSZ // // 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 @@ -611,7 +611,8 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, struct /* Verify that the radio channel number is valid. */ /* Any sort of medium should be OK here. */ - if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE) { + if ((chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE) + && save_audio_config_p->chan_medium[chan] != MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid transmit channel %d from KISS client app.\n", chan); dw_printf ("\n"); diff --git a/src/tq.c b/src/tq.c index 3d1b056..c656af5 100644 --- a/src/tq.c +++ b/src/tq.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2014, 2015, 2016 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2014, 2015, 2016, 2023 John Langner, WB2OSZ // // 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 @@ -50,7 +50,8 @@ #include "audio.h" #include "tq.h" #include "dedupe.h" - +#include "igate.h" +#include "dtime_now.h" @@ -195,6 +196,9 @@ void tq_init (struct audio_s *audio_config_p) * * Inputs: chan - Channel, 0 is first. * + * New in 1.7: + * Channel can be assigned to IGate rather than a radio. + * * prio - Priority, use TQ_PRIO_0_HI for digipeated or * TQ_PRIO_1_LO for normal. * @@ -247,6 +251,43 @@ void tq_append (int chan, int prio, packet_t pp) } #endif +// New in 1.7 - A channel can be assigned to the IGate rather than a radio. + +#ifndef DIGITEST // avoid dtest link error + + if (save_audio_config_p->chan_medium[chan] == MEDIUM_IGATE) { + + char ts[100]; // optional time stamp. + + if (strlen(save_audio_config_p->timestamp_format) > 0) { + char tstmp[100]; + timestamp_user_format (tstmp, sizeof(tstmp), save_audio_config_p->timestamp_format); + strlcpy (ts, " ", sizeof(ts)); // space after channel. + strlcat (ts, tstmp, sizeof(ts)); + } + else { + strlcpy (ts, "", sizeof(ts)); + } + + char stemp[256]; // Formated addresses. + ax25_format_addrs (pp, stemp); + unsigned char *pinfo; + int info_len = ax25_get_info (pp, &pinfo); + text_color_set(DW_COLOR_XMIT); + dw_printf ("[%d>is%s] ", chan, ts); + dw_printf ("%s", stemp); /* stations followed by : */ + ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); + dw_printf ("\n"); + + igate_send_rec_packet (chan, pp); + ax25_delete(pp); + return; + } +#endif + +// Normal case - put in queue for radio transmission. +// Error if trying to transmit to a radio channel which was not configured. + if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan); @@ -281,8 +322,6 @@ void tq_append (int chan, int prio, packet_t pp) * The check would allow an unlimited number of other types. * * Limit was 20. Changed to 100 in version 1.2 as a workaround. - * - * Implementing the 6PACK protocol is probably the proper solution. */ if (ax25_is_aprs(pp) && tq_count(chan,prio,"","",0) > 100) {