
		A PROPOSAL FOR TIMESTAMPS AT APRSIS

It can be taken as granted that today at least server systems are
running with NTP service keeping their system clocks within a few
milliseconds of UTC time.

Systems utilized as APRS IGATE should be able to utilize NTP service
as well, and keep their internal time well disciplined.

SimpleNTP clients are available even for embedded system code bases
making all kinds of internet capable systems able to have high quality
time management.

Timestamps are added on APRS text lines as prefixes of 8 characters.
Eighth character is always ':'.  Details are in "ENCODING" part.



		TRANSPORT COMPATIBILITY

As APRSIS clients would not know at first what to do with the timestamps,
a transport compatibility must be introduced:

	* APRSIS server announces on its connection message that it
	  is TIMESTAMP capable, and clients MUST NOT send timestamps
	  to APRSIS server without that capability

		# telnet aprsisserver 14580
		Connected to aprsisserver
		Escape character is '^]'.
		# javAPRSSrvr 4.0b1 [APRSIS TIMESTAMP]

	  Capability announcements follow style of IMAPv4 protocol,
	  where capability tokens are in square brackets, and are
	  separated by space character.

	* If a client does not send timestamped data to APRSIS, APRSIS
	  server adds the timestamp upon receiving the line.

	  APRSIS server does this conditional timestamping also for packets
	  received via UDP for initial deployment compatibility.

	* An APRSIS server connected to upstream with TCP is a client,
	  and must strip received/internal timestamps from packets sent to
	  upstream, if the upstream does not announce TIMESTAMP capability.

	  If upstream does not send timestamps for any reason, APRSIS server
	  adds it locally.

	* A client that has not announced timestamp awareness does
	  not receive timestamps from APRSIS, and furthermore, APRSIS is
	  filtering all packets with maximum age of 15 seconds.

	* A client that sends to APRSIS a text line with recognized
	  timestamp will get also timestampped lines sent up to itself.

	  No maximum age filtering is applied to clients that are timestamp
	  capable, and therefore those clients must be able to do timestamp
	  age analysis themselves.

	  Special timestamp-line  "AAAAAAA:\n"  tells APRSIS server, that
	  the client is APRSIS aware, and while it has nothing to actually
          send to APRSIS at this time, it does want all data sent to it to
	  contain timestamps.


APRSIS server software needs a configuration parameter

       udptimestamps = <boolean>

which defaults to "false".  When the value is "false" the software will
not send timestampped data to core peer machines over UDP.


There probably is no need for an Adjunct Filter that knows about timestamps.



		INITIAL DEPLOYMENT OF TIMESTAMP CAPABILITY

Because APRSIS core uses UDP protocol, and thus can not be aware of remote
node capabilities, the deployment of timestamp aware APRSIS servers at
APRSIS core has two phases:

	1) Software has configuration parameter   "udptimestamps"
	   non defined or defined as "false".

	2) After all core APRSIS systems are running timestamp
	   aware software, the configuration parameter can be
	   changed to   "udptimestamps = true"  and APRSIS server
	   starts to send timestamped packets over UDP to all of
           its peers.  -->  peer machines do not need to locally
	   add timestamps at reception time.


Everybody else can take TIMESTAMP capable system into use at any time
that is convenient to them, unless communication involves UDP.


		ENCODING

Use NTP timestamp (see RFC 2030) based time abstraction:

   Since NTP timestamps are cherished data and, in fact, represent the
   main product of the protocol, a special timestamp format has been
   established. NTP timestamps are represented as a 64-bit unsigned
   fixed-point number, in seconds relative to 0h on 1 January 1900. The
   integer part is in the first 32 bits and the fraction part in the
   last 32 bits. In the fraction part, the non-significant low order can
   be set to 0.

Take highest 32+10 bits of the timestamp, and encode those in BASE64
characters, most significant character first.  This produces 7 encoded
characters with time resolution of 1/1024 seconds.  With 6 encoded
characters the time resolution would be 1/16, which is felt to be
too coarse.

A hex encoded byte sequence of NTP timestamp looks like this:

	cf b4 ff a4 b5 a8 8b f9

it represent timestamp 3484745636.709603071 (2010/06/05 19:53:56)
and encoded timestamp value is: 

	z7T/pLW


The APRSIS passes around APRS messages as lines of text:

	OH2JCQ>APX195,TCPIP*,qAC,T2FINLAND:=6013.63N/02445.59E-Jani

Adding 7 character encoded timestamp + ":" character in the beginning
of a line would make this:

	z7T/pLW:OH2JCQ>APX195,TCPIP*,qAC,T2FINLAND:=6013.63N/02445.59E-Jani

There 8th character is always ':', and preceding 7 bytes present _current_
timestamp at 1/1024 second resolution.

Timestamp value "AAAAAAA:" is "no timestamp" (decodes as time 0.0) and
a client can use it to tell that it has no idea of timestamp, but it
wants to receive timestamped data.  An APRSIS data collector can enable
timestamp reception by sending line: "AAAAAAA:\n".


Packets with timestamps outside -1 to +N seconds limits are sign that
communication is retrying too much, or otherwise delayed, and such packets
are to be discarded.  (Services like APRSFI, FINDU et.al. may want even
older packets!)

To be compatible with NTP Era roll-over, the timestamp must really be
treated as 64-bit unsigned long integer, and compared with twos complement
arithmetic using overflows.


		TIMESTAMP ENCODING CODE EXAMPLE

Example UNIX system C code to produce and encode a timestamp:

  // Time Base Conversion Macros
  //
  // The NTP timebase is 00:00 Jan 1 1900.  The local
  // time base is 00:00 Jan 1 1970.  Convert between
  // these two by added or substracting 70 years
  // worth of time.  Note that 17 of these years were
  // leap years.
  
  #define TIME_BASEDIFF           (((70U*365U + 17U) * 24U*3600U))
  #define TIME_LOCAL_TO_NTP(t)    ((t)+TIME_BASEDIFF)
  
  void unix_tv_to_ntp(struct timeval *tv, uint64_t *ntp) {
    // Reciprocal conversion of tv_usec to fractional NTP seconds
    // Multiply tv_usec by  ((2^64)/1_000_000) / (2^32)
    uint64_t fract =  18446744073709ULL * (uint32_t)(tv->tv_usec);
    // Scale it back by 32 bit positions
    fract >>= 32;
    // Place seconds on upper 32 bits
    fract += ((uint64_t)TIME_LOCAL_TO_NTP(tv->tv_sec)) << 32;
    // Deliver time to caller
    *ntp  = fract;
  }

  static const char *BASE64EncodingDictionary =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "abcdefghijklmnopqrstuvwxyz"
    "0123456789"
    "+/";
  
  void encode_aprsis_ntptimestamp(uint64_t ntptime, char timestamp[8])
  {
  	int i;
  
  	ntptime >>= 22; // scale to 1/1024 seconds
  	for (i = 6; i >= 0; --i) {
  	    int n = (((int)ntptime) & 0x3F); // lowest 6 bits
  	    // printf("  [n=%d]\n", n);
  	    ntptime >>= 6;
  	    timestamp[i] = BASE64EncodingDictionary[n];
  	}
  	timestamp[7] = 0;
  }
  
  static const int8_t BASE64DecodingDictionary[128] =
    { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
      -1, -1, -1, -1,  // ' ', '!', '"', '#'
      -1, -1, -1, -1,  // '$', '%', '&'', '\''
      -1, -1, -1, 62,  // '(', ')', '*', '+',
      -1, -1, -1, 63,  // ',', '-', '.', '/'
      52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // '0' .. '9'
      -1, -1, -1, -1, -1, -1, // ':', ';', '<', '=', '>', '?'
      -1,  0,  1,  2,  3,  4,  5,  6, // '@', 'A' .. 'G'
       7,  8,  9, 10, 11, 12, 13, 14, // 'H' .. 'O'
      15, 16, 17, 18, 19, 20, 21, 22, // 'P' .. 'W'
      23, 24, 25, -1, -1, -1, -1, -1, // 'X'..'Z', '[', '\\', ']', '^', '_'
      -1, 26, 27, 28, 29, 30, 31, 32, // '`', 'a' .. 'g'
      33, 34, 35, 36, 37, 38, 39, 40, // 'h' .. 'o'
      41, 42, 43, 44, 45, 46, 47, 48, // 'p' .. 'w'
      49, 50, 51, -1, -1, -1, -1, -1 }; // 'x'..'z', ...
  
  
  int decode_aprsis_ntptimestamp(char timestamp[8], uint64_t *ntptimep)
  {
  	uint64_t ntptime = 0;
  
  	int i, n;
  	char c;
  
  	for (i = 0; i < 7; ++i) {
  	  c = timestamp[i];
  	  if (c <= 0 || c > 127) return -1; // BARF!
  	  n = BASE64DecodingDictionary[(int)c];
  	  // printf("  [n=%d]\n", n);
  	  if (n < 0) {
  	    // Should not happen!
  	    return -1; // Decode fail!
  	  }
  
  	  ntptime <<= 6;
  	  ntptime |= n;
  	}
  	ntptime <<= 22;
  	*ntptimep = ntptime;
  	return 0; // Decode OK
  }


You may choose to produce timestamps with coarser resolution than 1/1024
seconds. Let least significant bits to be zero.  The about 1 millisecond
resolution is a compromise in between 1 second, and sub-nanosecond
resolutions.

A heavy producer and consumer of timestamps, like an APRSIS server, may have
single thread waking up 10 times per second and producing a new timestamp
object every time, and overwriting global storage pointer to "current" one.
For access there is no need to do any sort of synchronizations.
Then received messages just copy that timestamp if necessary, and not run
its production by themselves.

Note: There is no need to convert NTP timestamps to local time in this
application.  It is quite enough that system receiving NTP timestamps
does regularly create current reference NTP timestamp to be able to
determine offset from received timestamp to reference time.
