@@ -60,13 +60,15 @@ a transport compatibility must be introduced:
60
60
age analysis themselves.
61
61
62
62
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
65
65
contain timestamps.
66
66
67
67
68
68
APRSIS server software needs a configuration parameter
69
+
69
70
udptimestamps = <boolean>
71
+
70
72
which defaults to "false". When the value is "false" the software will
71
73
not send timestampped data to core peer machines over UDP.
72
74
@@ -87,12 +89,13 @@ APRSIS core has two phases:
87
89
2) After all core APRSIS systems are running timestamp
88
90
aware software, the configuration parameter can be
89
91
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.
92
95
93
- Everybody else can take TIMESTAMP capable system into use at a time that is
94
- convenient to them, unless communication involves UDP.
95
96
97
+ Everybody else can take TIMESTAMP capable system into use at any time
98
+ that is convenient to them, unless communication involves UDP.
96
99
97
100
98
101
ENCODING
@@ -109,7 +112,9 @@ Use NTP timestamp (see RFC 2030) based time abstraction:
109
112
110
113
Take highest 32+10 bits of the timestamp, and encode those in BASE64
111
114
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.
113
118
114
119
A hex encoded byte sequence of NTP timestamp looks like this:
115
120
@@ -128,7 +133,7 @@ The APRSIS passes around APRS messages as lines of text:
128
133
Adding 7 character encoded timestamp + ":" character in the beginning
129
134
of a line would make this:
130
135
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
132
137
133
138
There 8th character is always ':', and preceding 7 bytes present _current_
134
139
timestamp at 1/1024 second resolution.
@@ -142,7 +147,7 @@ timestamp reception by sending line: "AAAAAAA:\n".
142
147
Packets with timestamps outside -1 to +N seconds limits are sign that
143
148
communication is retrying too much, or otherwise delayed, and such packets
144
149
are to be discarded. (Services like APRSFI, FINDU et.al. may want even
145
- old packets!)
150
+ older packets!)
146
151
147
152
To be compatible with NTP Era roll-over, the timestamp must really be
148
153
treated as 64-bit unsigned long integer, and compared with twos complement
@@ -153,28 +158,107 @@ arithmetic using overflows.
153
158
154
159
Example UNIX system C code to produce and encode a timestamp:
155
160
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
+ }
172
247
173
248
174
249
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.
176
253
177
254
A heavy producer and consumer of timestamps, like an APRSIS server, may have
178
255
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