Skip to content

Commit f28aaba

Browse files
author
oh2mqk
committed
TIMESTAMP-AT-APRSIS proposal text editing
1 parent 7e191bf commit f28aaba

File tree

1 file changed

+112
-28
lines changed

1 file changed

+112
-28
lines changed

TIMESTAMP-AT-APRSIS

Lines changed: 112 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,15 @@ a transport compatibility must be introduced:
6060
age analysis themselves.
6161

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

6767

6868
APRSIS server software needs a configuration parameter
69+
6970
udptimestamps = <boolean>
71+
7072
which defaults to "false". When the value is "false" the software will
7173
not send timestampped data to core peer machines over UDP.
7274

@@ -87,12 +89,13 @@ APRSIS core has two phases:
8789
2) After all core APRSIS systems are running timestamp
8890
aware software, the configuration parameter can be
8991
changed to "udptimestamps = true" and APRSIS server
90-
starts to send timestamped packets over UDP to its peers.
91-
92+
starts to send timestamped packets over UDP to all of
93+
its peers. --> peer machines do not need to locally
94+
add timestamps at reception time.
9295

93-
Everybody else can take TIMESTAMP capable system into use at a time that is
94-
convenient to them, unless communication involves UDP.
9596

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

97100

98101
ENCODING
@@ -109,7 +112,9 @@ Use NTP timestamp (see RFC 2030) based time abstraction:
109112

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

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

@@ -128,7 +133,7 @@ The APRSIS passes around APRS messages as lines of text:
128133
Adding 7 character encoded timestamp + ":" character in the beginning
129134
of a line would make this:
130135

131-
Tstamp/:OH2JCQ>APX195,TCPIP*,qAC,T2FINLAND:=6013.63N/02445.59E-Jani
136+
z7T/pLW:OH2JCQ>APX195,TCPIP*,qAC,T2FINLAND:=6013.63N/02445.59E-Jani
132137

133138
There 8th character is always ':', and preceding 7 bytes present _current_
134139
timestamp at 1/1024 second resolution.
@@ -142,7 +147,7 @@ timestamp reception by sending line: "AAAAAAA:\n".
142147
Packets with timestamps outside -1 to +N seconds limits are sign that
143148
communication is retrying too much, or otherwise delayed, and such packets
144149
are to be discarded. (Services like APRSFI, FINDU et.al. may want even
145-
old packets!)
150+
older packets!)
146151

147152
To be compatible with NTP Era roll-over, the timestamp must really be
148153
treated as 64-bit unsigned long integer, and compared with twos complement
@@ -153,28 +158,107 @@ arithmetic using overflows.
153158

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

156-
uint64_t ntptime;
157-
struct timeval tv;
158-
char timestamp[8];
159-
int i;
160-
161-
gettimeofday(&tv, NULL);
162-
ntptime = ((uint64_t)(TIME_LOCAL_TO_NTP(tv.tv_sec))) << 32
163-
+ microseconds_to_ntp_picoseconds(tv.tv_usec);
164-
165-
ntptime >>= 22; // scale to 1/1024 seconds
166-
for (i = 6; i >= 0; --i) {
167-
int n = (((int)ntptime) & 0x3F); // lowest 6 bits
168-
ntptime >>= 6;
169-
timestamp[i] = BASE64EncodingDictionary[n];
170-
}
171-
timestamp[7] = 0;
161+
// Time Base Conversion Macros
162+
//
163+
// The NTP timebase is 00:00 Jan 1 1900. The local
164+
// time base is 00:00 Jan 1 1970. Convert between
165+
// these two by added or substracting 70 years
166+
// worth of time. Note that 17 of these years were
167+
// leap years.
168+
169+
#define TIME_BASEDIFF (((70U*365U + 17U) * 24U*3600U))
170+
#define TIME_LOCAL_TO_NTP(t) ((t)+TIME_BASEDIFF)
171+
172+
void unix_tv_to_ntp(struct timeval *tv, uint64_t *ntp) {
173+
// Reciprocal conversion of tv_usec to fractional NTP seconds
174+
// Multiply tv_usec by ((2^64)/1_000_000) / (2^32)
175+
uint64_t fract = 18446744073709ULL * (uint32_t)(tv->tv_usec);
176+
// Scale it back by 32 bit positions
177+
fract >>= 32;
178+
// Place seconds on upper 32 bits
179+
fract += ((uint64_t)TIME_LOCAL_TO_NTP(tv->tv_sec)) << 32;
180+
// Deliver time to caller
181+
*ntp = fract;
182+
}
183+
184+
static const char *BASE64EncodingDictionary =
185+
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
186+
"abcdefghijklmnopqrstuvwxyz"
187+
"0123456789"
188+
"+/";
189+
190+
void encode_aprsis_ntptimestamp(uint64_t ntptime, char timestamp[8])
191+
{
192+
int i;
193+
194+
ntptime >>= 22; // scale to 1/1024 seconds
195+
for (i = 6; i >= 0; --i) {
196+
int n = (((int)ntptime) & 0x3F); // lowest 6 bits
197+
// printf(" [n=%d]\n", n);
198+
ntptime >>= 6;
199+
timestamp[i] = BASE64EncodingDictionary[n];
200+
}
201+
timestamp[7] = 0;
202+
}
203+
204+
static const int8_t BASE64DecodingDictionary[128] =
205+
{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
206+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
207+
-1, -1, -1, -1, // ' ', '!', '"', '#'
208+
-1, -1, -1, -1, // '$', '%', '&'', '\''
209+
-1, -1, -1, 62, // '(', ')', '*', '+',
210+
-1, -1, -1, 63, // ',', '-', '.', '/'
211+
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // '0' .. '9'
212+
-1, -1, -1, -1, -1, -1, // ':', ';', '<', '=', '>', '?'
213+
-1, 0, 1, 2, 3, 4, 5, 6, // '@', 'A' .. 'G'
214+
7, 8, 9, 10, 11, 12, 13, 14, // 'H' .. 'O'
215+
15, 16, 17, 18, 19, 20, 21, 22, // 'P' .. 'W'
216+
23, 24, 25, -1, -1, -1, -1, -1, // 'X'..'Z', '[', '\\', ']', '^', '_'
217+
-1, 26, 27, 28, 29, 30, 31, 32, // '`', 'a' .. 'g'
218+
33, 34, 35, 36, 37, 38, 39, 40, // 'h' .. 'o'
219+
41, 42, 43, 44, 45, 46, 47, 48, // 'p' .. 'w'
220+
49, 50, 51, -1, -1, -1, -1, -1 }; // 'x'..'z', ...
221+
222+
223+
int decode_aprsis_ntptimestamp(char timestamp[8], uint64_t *ntptimep)
224+
{
225+
uint64_t ntptime = 0;
226+
227+
int i, n;
228+
char c;
229+
230+
for (i = 0; i < 7; ++i) {
231+
c = timestamp[i];
232+
if (c <= 0 || c > 127) return -1; // BARF!
233+
n = BASE64DecodingDictionary[(int)c];
234+
// printf(" [n=%d]\n", n);
235+
if (n < 0) {
236+
// Should not happen!
237+
return -1; // Decode fail!
238+
}
239+
240+
ntptime <<= 6;
241+
ntptime |= n;
242+
}
243+
ntptime <<= 22;
244+
*ntptimep = ntptime;
245+
return 0; // Decode OK
246+
}
172247

173248

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

177254
A heavy producer and consumer of timestamps, like an APRSIS server, may have
178255
single thread waking up 10 times per second and producing a new timestamp
179-
object every time. Then received messages just copy that timestamp if
180-
necessary, and not run its production by themselves.
256+
object every time, and overwriting global storage pointer to "current" one.
257+
For access there is no need to do any sort of synchronizations.
258+
Then received messages just copy that timestamp if necessary, and not run
259+
its production by themselves.
260+
261+
Note: There is no need to convert NTP timestamps to local time in this
262+
application. It is quite enough that system receiving NTP timestamps
263+
does regularly create current reference NTP timestamp to be able to
264+
determine offset from received timestamp to reference time.

0 commit comments

Comments
 (0)