Skip to content

Commit 54114b5

Browse files
committed
Rewrite sntp in Lua with only a little C
1 parent 76e9f1a commit 54114b5

File tree

4 files changed

+792
-1
lines changed

4 files changed

+792
-1
lines changed

app/modules/sntppkt.c

+388
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
/*
2+
* Copyright 2015 Dius Computing Pty Ltd. All rights reserved.
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions
6+
* are met:
7+
*
8+
* - Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* - Redistributions in binary form must reproduce the above copyright
11+
* notice, this list of conditions and the following disclaimer in the
12+
* documentation and/or other materials provided with the
13+
* distribution.
14+
* - Neither the name of the copyright holders nor the names of
15+
* its contributors may be used to endorse or promote products derived
16+
* from this software without specific prior written permission.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21+
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22+
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23+
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24+
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26+
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27+
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29+
* OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*
31+
* @author Johny Mattsson <[email protected]>
32+
* @author Nathaniel Wesley Filardo <[email protected]>
33+
*/
34+
35+
// Module for Simple Network Time Protocol (SNTP) packet processing;
36+
// see lua_modules/sntp/sntp.lua for the user-friendly bits of this.
37+
38+
#include "module.h"
39+
#include "lauxlib.h"
40+
#include "lmem.h"
41+
#include "os_type.h"
42+
#include "osapi.h"
43+
#include "lwip/udp.h"
44+
#include <stdlib.h>
45+
#include "user_modules.h"
46+
#include "lwip/dns.h"
47+
#include "task/task.h"
48+
#include "user_interface.h"
49+
50+
#define max(a,b) ((a < b) ? b : a)
51+
52+
#define NTP_PORT 123
53+
#define NTP_ANYCAST_ADDR(dst) IP4_ADDR(dst, 224, 0, 1, 1)
54+
55+
#if 0
56+
# define sntppkt_dbg(...) dbg_printf(__VA_ARGS__)
57+
#else
58+
# define sntppkt_dbg(...)
59+
#endif
60+
61+
typedef struct
62+
{
63+
uint32_t sec;
64+
uint32_t frac;
65+
} ntp_timestamp_t;
66+
67+
static const uint32_t NTP_TO_UNIX_EPOCH = 2208988800ul;
68+
69+
typedef struct
70+
{
71+
uint8_t mode : 3;
72+
uint8_t ver : 3;
73+
uint8_t LI : 2;
74+
uint8_t stratum;
75+
uint8_t poll;
76+
uint8_t precision;
77+
uint32_t delta_r;
78+
uint32_t epsilon_r;
79+
uint32_t refid;
80+
ntp_timestamp_t ref;
81+
ntp_timestamp_t origin;
82+
ntp_timestamp_t recv;
83+
ntp_timestamp_t xmit;
84+
} __attribute__((packed)) ntp_frame_t;
85+
86+
#define NTP_RESPONSE_METATABLE "sntppkt.resp"
87+
typedef struct {
88+
/* Copied from incoming packet */
89+
uint32_t delta_r;
90+
uint32_t epsilon_r;
91+
uint8_t LI;
92+
uint8_t stratum;
93+
94+
/* Computed as per RFC 5905; units are 2^(-32) seconds */
95+
int64_t theta;
96+
int64_t delta;
97+
98+
/* Local computation */
99+
uint32_t rx_s;
100+
uint32_t rx_us;
101+
uint32_t cached_delta;
102+
} ntp_response_t;
103+
104+
static uint64_t
105+
sntppkt_div1m(uint64_t n) {
106+
uint64_t q1 = (n >> 5) + (n >> 10);
107+
uint64_t q2 = (n >> 12) + (q1 >> 1);
108+
uint64_t q3 = (q2 >> 11) - (q2 >> 23);
109+
110+
uint64_t q = n + q1 + q2 - q3;
111+
112+
q = q >> 20;
113+
114+
// Ignore the error term -- it is measured in pico seconds
115+
return q;
116+
}
117+
118+
static uint32_t
119+
sntppkt_us_to_frac(uint64_t us) {
120+
return sntppkt_div1m(us << 32);
121+
}
122+
123+
static const uint32_t MICROSECONDS = 1000000;
124+
125+
static uint32_t
126+
sntppkt_frac16_to_us(uint64_t frac) {
127+
return (frac * MICROSECONDS) >> 16;
128+
}
129+
130+
/*
131+
* Convert sec/usec to a Lua string suitable for depositing into a SNTP packet
132+
* buffer. This is a little gross, but it's not the worst thing a C
133+
* programmer's ever done, I'm sure.
134+
*/
135+
static int
136+
sntppkt_make_ts(lua_State *L) {
137+
ntp_timestamp_t ts;
138+
uint32_t usec;
139+
140+
ts.sec = htonl(luaL_checkinteger(L, 1) + NTP_TO_UNIX_EPOCH) ;
141+
usec = luaL_checkinteger(L, 2) ;
142+
ts.frac = htonl(sntppkt_us_to_frac(usec));
143+
144+
lua_pushlstring(L, (const char *)&ts, sizeof(ts));
145+
return 1;
146+
}
147+
148+
/*
149+
* Process a SNTP packet as contained in a Lua string, given a cookie timestamp
150+
* and local clock second*usecond pair. Generates a ntp_response_t userdata
151+
* for later processing or a string if the server is telling us to go away.
152+
*
153+
* :: string (packet)
154+
* -> string (cookie)
155+
* -> int (local clock, sec component)
156+
* -> int (local clock, usec component)
157+
* -> sntppkt.resp
158+
*
159+
*/
160+
static int
161+
sntppkt_proc_pkt(lua_State *L) {
162+
const char *pkts;
163+
size_t pkts_len;
164+
165+
uint32_t now_sec;
166+
uint32_t now_usec;
167+
168+
ntp_timestamp_t *cookie;
169+
size_t cookie_len;
170+
171+
ntp_response_t *ntpr;
172+
173+
// make sure we have an aligned copy to work from
174+
// XXX nwf: is this necessary?
175+
ntp_frame_t pktb;
176+
177+
now_usec = luaL_checkinteger(L, 4);
178+
now_sec = luaL_checkinteger(L, 3);
179+
180+
luaL_checktype(L, 2, LUA_TSTRING);
181+
cookie = (ntp_timestamp_t*) lua_tolstring(L, 2, &cookie_len);
182+
if (cookie_len != sizeof(*cookie)) {
183+
luaL_error(L, "Bad expected cookie");
184+
}
185+
186+
luaL_checktype(L, 1, LUA_TSTRING);
187+
pkts = lua_tolstring(L, 1, &pkts_len);
188+
if (pkts_len != sizeof(pktb)) {
189+
luaL_error(L, "Bad packet length");
190+
}
191+
os_memcpy (&pktb, pkts, sizeof(pktb));
192+
193+
if (memcmp((const char *)cookie, (const char *)&pktb.origin, sizeof (*cookie))) {
194+
/* bad cookie; return nil */
195+
return 0;
196+
}
197+
198+
/* KOD? */
199+
if (pktb.LI == 3) {
200+
lua_pushlstring(L, (const char *)&pktb.refid, 4);
201+
return 1;
202+
}
203+
204+
ntpr = lua_newuserdata(L, sizeof(ntp_response_t));
205+
luaL_getmetatable(L, NTP_RESPONSE_METATABLE);
206+
lua_setmetatable(L, -2);
207+
208+
ntpr->rx_s = now_sec;
209+
ntpr->rx_us = now_usec;
210+
ntpr->LI = pktb.LI;
211+
ntpr->stratum = pktb.stratum;
212+
213+
// NTP Short Format: 16 bit seconds, 16 bit fraction
214+
ntpr->delta_r = ntohl(pktb.delta_r);
215+
ntpr->epsilon_r = ntohl(pktb.epsilon_r);
216+
217+
/* Heavy time lifting time */
218+
219+
// NTP Long Format: 32 bit seconds, 32 bit fraction
220+
pktb.origin.sec = ntohl(pktb.origin.sec);
221+
pktb.origin.frac = ntohl(pktb.origin.frac);
222+
pktb.recv.sec = ntohl(pktb.recv.sec);
223+
pktb.recv.frac = ntohl(pktb.recv.frac);
224+
pktb.xmit.sec = ntohl(pktb.xmit.sec);
225+
pktb.xmit.frac = ntohl(pktb.xmit.frac);
226+
227+
// When we sent it (our clock)
228+
uint64_t ntp_origin = (((uint64_t) pktb.origin.sec ) << 32)
229+
+ pktb.origin.frac;
230+
// When they got it (their clock)
231+
uint64_t ntp_recv = (((uint64_t) pktb.recv.sec ) << 32)
232+
+ pktb.recv.frac;
233+
// When they replied (their clock)
234+
uint64_t ntp_xmit = (((uint64_t) pktb.xmit.sec ) << 32)
235+
+ pktb.xmit.frac;
236+
// When we got it back (our clock)
237+
uint64_t ntp_dest = (((uint64_t) now_sec + NTP_TO_UNIX_EPOCH ) << 32)
238+
+ sntppkt_us_to_frac(now_usec);
239+
240+
// | outgoing offset | | incoming offset |
241+
ntpr->theta = (int64_t)(ntp_recv - ntp_origin) / 2 + (int64_t)(ntp_xmit - ntp_dest) / 2;
242+
243+
// | our clock delta | | their clock delta |
244+
ntpr->delta = (int64_t)(ntp_dest - ntp_origin) / 2 + (int64_t)(ntp_xmit - ntp_recv) / 2;
245+
246+
ntpr->cached_delta = ntpr->delta_r + (ntpr->delta >> 17);
247+
248+
sntppkt_dbg("SNTPPKT PROC n_r=%llx n_o=%llx n_x=%llx n_d=%llx th=%llx "
249+
"d=%llx + %lx = cd=%lx\n",
250+
ntp_recv, ntp_origin, ntp_xmit, ntp_dest, ntpr->theta,
251+
ntpr->delta, ntpr->delta_r, ntpr->cached_delta);
252+
253+
return 1;
254+
}
255+
256+
/*
257+
* Left-biased selector of a "preferred" NTP response. Note that preference
258+
* is rather subjective!
259+
*
260+
* Returns true iff we'd prefer the second response to the first.
261+
*
262+
* :: sntppkt.resp -> sntppkt.resp -> boolean
263+
*/
264+
265+
static int
266+
sntppkt_resp_pick(lua_State *L) {
267+
268+
ntp_response_t *a = luaL_checkudata(L, 1, NTP_RESPONSE_METATABLE);
269+
ntp_response_t *b = luaL_checkudata(L, 2, NTP_RESPONSE_METATABLE);
270+
int biased = 0;
271+
272+
biased = lua_toboolean(L, 3);
273+
274+
/*
275+
* If we're "biased", prefer the second structure if the delay less than
276+
* 3/4ths of the delay in the first. An unbiased comparison just uses
277+
* the raw delay values.
278+
*/
279+
if (biased) {
280+
lua_pushboolean(L, a->cached_delta * 3 > b->cached_delta * 4);
281+
} else {
282+
lua_pushboolean(L, a->cached_delta > b->cached_delta );
283+
}
284+
return 1;
285+
}
286+
287+
static void
288+
field_from_number(lua_State *L, const char * field_name, lua_Number value) {
289+
lua_pushnumber(L, value);
290+
lua_setfield(L, -2, field_name);
291+
}
292+
293+
/*
294+
* Inflate a NTP response into a Lua table
295+
*
296+
* :: sntppkt.resp -> { }
297+
*/
298+
static int
299+
sntppkt_resp_totable(lua_State *L) {
300+
ntp_response_t *r = luaL_checkudata(L, 1, NTP_RESPONSE_METATABLE);
301+
302+
lua_createtable(L, 0, 6);
303+
304+
sntppkt_dbg("SNTPPKT READ th=%llx\n", r->theta);
305+
306+
field_from_number(L, "theta_s", r->theta >> 32);
307+
field_from_number(L, "theta_us", ((r->theta & 0xFFFFFFFF) * MICROSECONDS) >> 32);
308+
309+
field_from_number(L, "delta", r->delta >> 16);
310+
field_from_number(L, "delta_r", r->delta_r);
311+
field_from_number(L, "epsilon_r", r->epsilon_r);
312+
313+
field_from_number(L, "leapind", r->LI);
314+
field_from_number(L, "stratum", r->stratum);
315+
field_from_number(L, "rx_s" , r->rx_s);
316+
field_from_number(L, "rx_us" , r->rx_us);
317+
318+
return 1;
319+
}
320+
321+
/*
322+
* Compute local RTC drift rate given a SNTP response, previous sample time,
323+
* and error integral value. Returns new rate and integral value.
324+
*
325+
* Results are only sensible if resp->theta is sufficiently small (i.e., less
326+
* than a second) and the inter-sample duration must, of course, be positive.
327+
*
328+
* :: sntppkt.resp
329+
* -> int (prior sample time, seconds component)
330+
* -> int (prior sample time, microseconds component)
331+
* -> int (integral)
332+
* -> int (rate), int (integral)
333+
*
334+
*/
335+
static int
336+
sntppkt_resp_drift_compensate(lua_State *L) {
337+
ntp_response_t *resp = luaL_checkudata(L, 1, NTP_RESPONSE_METATABLE);
338+
339+
int32_t theta32 = resp->theta >> 32;
340+
if (theta32 != 0 && theta32 != -1) {
341+
return luaL_error(L, "Large deviation");
342+
}
343+
344+
uint32_t prior_s = luaL_checkinteger(L, 2);
345+
uint32_t prior_us = luaL_checkinteger(L, 3);
346+
int32_t err_int = luaL_checkinteger(L, 4);
347+
348+
uint64_t prior = ((uint64_t)prior_s << 32) + sntppkt_us_to_frac( prior_us);
349+
uint64_t rx = ((uint64_t)resp->rx_s << 32) + sntppkt_us_to_frac(resp->rx_us);
350+
351+
int64_t isdur = rx - prior;
352+
if (isdur <= 0) {
353+
return luaL_error(L, "Negative time base");
354+
}
355+
356+
/* Compute our drift rate over 2 */
357+
int32_t drift2 = ((resp->theta << 31) / isdur) >> 32;
358+
359+
int32_t newrate = drift2 * 2 + err_int;
360+
err_int += drift2 >> 1;
361+
362+
lua_pushnumber(L, newrate);
363+
lua_pushnumber(L, err_int);
364+
return 2;
365+
}
366+
367+
LROT_BEGIN(sntppkt_resp)
368+
LROT_FUNCENTRY( pick, sntppkt_resp_pick )
369+
LROT_FUNCENTRY( totable, sntppkt_resp_totable )
370+
LROT_FUNCENTRY( drift_compensate, sntppkt_resp_drift_compensate )
371+
372+
LROT_TABENTRY( __index, sntppkt_resp )
373+
LROT_END(sntppkt_resp, sntppkt_resp, 0)
374+
375+
static int
376+
sntppkt_init(lua_State *L)
377+
{
378+
luaL_rometatable(L, NTP_RESPONSE_METATABLE, LROT_TABLEREF(sntppkt_resp));
379+
return 0;
380+
}
381+
382+
// Module function map
383+
LROT_BEGIN(sntppkt)
384+
LROT_FUNCENTRY( make_ts , sntppkt_make_ts )
385+
LROT_FUNCENTRY( proc_pkt , sntppkt_proc_pkt )
386+
LROT_END( sntppkt, NULL, 0 )
387+
388+
NODEMCU_MODULE(SNTPPKT, "sntppkt", sntppkt, sntppkt_init);

0 commit comments

Comments
 (0)