Skip to content

Commit 5270121

Browse files
committed
DNM: WIP: Rewrite sntp in Lua with only a little C
Currently known defects: * Lots of print() debugging still present * No real handling of LI * No RTC rate support * Docs light, and none for sntppkt Doubtless plenty of others, too.
1 parent bc7ffb3 commit 5270121

File tree

3 files changed

+637
-0
lines changed

3 files changed

+637
-0
lines changed

app/modules/sntppkt.c

+315
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
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 "c_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 root_delay;
78+
uint32_t root_dispersion;
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+
typedef struct {
87+
int64_t delta;
88+
uint32_t cached_delay;
89+
uint32_t txsec;
90+
uint32_t delay_frac;
91+
uint32_t root_delay;
92+
uint32_t root_dispersion;
93+
uint8_t LI;
94+
uint8_t stratum;
95+
} ntp_response_t;
96+
97+
static uint64_t
98+
sntppkt_div1m(uint64_t n) {
99+
uint64_t q1 = (n >> 5) + (n >> 10);
100+
uint64_t q2 = (n >> 12) + (q1 >> 1);
101+
uint64_t q3 = (q2 >> 11) - (q2 >> 23);
102+
103+
uint64_t q = n + q1 + q2 - q3;
104+
105+
q = q >> 20;
106+
107+
// Ignore the error term -- it is measured in pico seconds
108+
return q;
109+
}
110+
111+
static uint32_t
112+
sntppkt_us_to_frac(uint64_t us) {
113+
return sntppkt_div1m(us << 32);
114+
}
115+
116+
static const uint32_t MICROSECONDS = 1000000;
117+
118+
static uint32_t
119+
sntppkt_frac16_to_us(uint64_t frac) {
120+
return (frac * MICROSECONDS) >> 16;
121+
}
122+
123+
/*
124+
* Convert sec/usec to a Lua string suitable for depositing into a SNTP packet
125+
* buffer. This is a little gross, but it's not the worst thing a C
126+
* programmer's ever done, I'm sure.
127+
*/
128+
static int
129+
sntppkt_make_ts(lua_State *L) {
130+
ntp_timestamp_t ts;
131+
uint32_t usec;
132+
133+
ts.sec = htonl(luaL_checkinteger(L, 1) + NTP_TO_UNIX_EPOCH) ;
134+
usec = luaL_checkinteger(L, 2) ;
135+
ts.frac = htonl(sntppkt_us_to_frac(usec));
136+
137+
lua_pushlstring(L, (const char *)&ts, sizeof(ts));
138+
return 1;
139+
}
140+
141+
/*
142+
* Process a SNTP packet as contained in a Lua string, given a cookie timestamp
143+
* and local clock second*usecond pair. Generates a ntp_response_t userdata
144+
* for later processing or a string if the server is telling us to go away.
145+
*/
146+
static int
147+
sntppkt_proc_pkt(lua_State *L) {
148+
const char *pkts;
149+
size_t pkts_len;
150+
151+
uint32_t now_sec;
152+
uint32_t now_usec;
153+
154+
ntp_timestamp_t *cookie;
155+
size_t cookie_len;
156+
157+
ntp_response_t *ntpr;
158+
159+
// make sure we have an aligned copy to work from
160+
// XXX nwf: is this necessary?
161+
ntp_frame_t pktb;
162+
163+
now_usec = luaL_checkinteger(L, 4);
164+
now_sec = luaL_checkinteger(L, 3);
165+
166+
luaL_checktype(L, 2, LUA_TSTRING);
167+
cookie = (ntp_timestamp_t*) lua_tolstring(L, 2, &cookie_len);
168+
if (cookie_len != sizeof(*cookie)) {
169+
luaL_error(L, "Bad cookie");
170+
}
171+
172+
luaL_checktype(L, 1, LUA_TSTRING);
173+
pkts = lua_tolstring(L, 1, &pkts_len);
174+
if (pkts_len != sizeof(pktb)) {
175+
luaL_error(L, "Bad packet length");
176+
}
177+
os_memcpy (&pktb, pkts, sizeof(pktb));
178+
179+
if (memcmp((const char *)cookie, (const char *)&pktb.origin, sizeof (*cookie))) {
180+
/* bad cookie; return nil */
181+
return 0;
182+
}
183+
184+
/* KOD? */
185+
if (pktb.LI == 3) {
186+
lua_pushlstring(L, (const char *)&pktb.refid, 4);
187+
return 1;
188+
}
189+
190+
ntpr = lua_newuserdata(L, sizeof(ntp_response_t));
191+
luaL_getmetatable(L, "sntppkt.resp");
192+
lua_setmetatable(L, -2);
193+
194+
ntpr->LI = pktb.LI;
195+
ntpr->stratum = pktb.stratum;
196+
ntpr->root_delay = ntohl(pktb.root_delay);
197+
ntpr->root_dispersion = ntohl(pktb.root_dispersion);
198+
199+
/* Heavy time lifting time */
200+
201+
pktb.origin.sec = ntohl(pktb.origin.sec);
202+
pktb.origin.frac = ntohl(pktb.origin.frac);
203+
pktb.recv.sec = ntohl(pktb.recv.sec);
204+
pktb.recv.frac = ntohl(pktb.recv.frac);
205+
pktb.xmit.sec = ntohl(pktb.xmit.sec);
206+
pktb.xmit.frac = ntohl(pktb.xmit.frac);
207+
208+
ntpr->txsec = pktb.xmit.sec - NTP_TO_UNIX_EPOCH;
209+
210+
uint64_t ntp_recv = (((uint64_t) pktb.recv.sec ) << 32)
211+
+ pktb.recv.frac;
212+
uint64_t ntp_origin = (((uint64_t) pktb.origin.sec ) << 32)
213+
+ pktb.origin.frac;
214+
uint64_t ntp_xmit = (((uint64_t) pktb.xmit.sec ) << 32)
215+
+ pktb.xmit.frac;
216+
uint64_t ntp_dest = (((uint64_t) now_sec + NTP_TO_UNIX_EPOCH ) << 32)
217+
+ sntppkt_us_to_frac(now_usec);
218+
219+
ntpr->delta = ((int64_t) ntp_recv - ntp_origin) / 2
220+
+ ((int64_t) ntp_xmit - ntp_dest ) / 2;
221+
222+
ntpr->delay_frac = ((int64_t)ntp_dest - ntp_origin - ntp_xmit + ntp_recv) >> 16;
223+
224+
ntpr->cached_delay = ntpr->root_delay * 2 + ntpr->delay_frac;
225+
226+
return 1;
227+
}
228+
229+
/*
230+
* Left-biased selector of a "preferred" NTP response. Note that preference
231+
* is rather subjective!
232+
*
233+
* Lua does not make it straightforward to return an existing userdata
234+
* object, so instead we merely return a boolean indicating whether the
235+
* second argument is superior to the first.
236+
*/
237+
238+
static int
239+
sntppkt_pick_resp(lua_State *L) {
240+
241+
ntp_response_t *a = luaL_checkudata(L, 1, "sntppkt.resp");
242+
ntp_response_t *b = luaL_checkudata(L, 2, "sntppkt.resp");
243+
int biased = 0;
244+
245+
biased = lua_toboolean(L, 3);
246+
247+
/*
248+
* If we're "biased", prefer the second structure if the delay less than
249+
* 3/4ths of the delay in the first. An unbiased comparison just uses
250+
* the raw delay values.
251+
*/
252+
if (biased) {
253+
lua_pushboolean(L, a->cached_delay * 3 > b->cached_delay * 4);
254+
} else {
255+
lua_pushboolean(L, a->cached_delay > b->cached_delay );
256+
}
257+
return 1;
258+
}
259+
260+
/*
261+
* Inflate a NTP response into a Lua table
262+
*/
263+
static int
264+
sntppkt_read_resp(lua_State *L) {
265+
ntp_response_t *r = luaL_checkudata(L, 1, "sntppkt.resp");
266+
267+
lua_createtable(L, 0, 6);
268+
269+
/* For large corrections, don't bother exposing fine values */
270+
int d40 = r->delta >> 40;
271+
if (d40 != 0 && d40 != -1) {
272+
lua_pushnumber(L, r->delta >> 32);
273+
lua_setfield(L, -2, "offset_s");
274+
} else {
275+
lua_pushnumber(L, (r->delta * MICROSECONDS) >> 32);
276+
lua_setfield(L, -2, "offset_us");
277+
}
278+
279+
lua_pushnumber(L, sntppkt_frac16_to_us(r->delay_frac));
280+
lua_setfield(L, -2, "delay_us");
281+
282+
lua_pushnumber(L, sntppkt_frac16_to_us(r->root_delay));
283+
lua_setfield(L, -2, "root_delay_us");
284+
285+
lua_pushnumber(L, r->root_dispersion);
286+
lua_setfield(L, -2, "root_dispersion");
287+
288+
lua_pushnumber(L, r->LI);
289+
lua_setfield(L, -2, "leapind");
290+
291+
lua_pushnumber(L, r->stratum);
292+
lua_setfield(L, -2, "stratum");
293+
294+
return 1;
295+
}
296+
297+
LROT_BEGIN(sntppkt_resp)
298+
LROT_END(sntppkt_resp, sntppkt_resp, 0)
299+
300+
static int
301+
sntppkt_init(lua_State *L)
302+
{
303+
luaL_rometatable(L, "sntppkt.resp" , LROT_TABLEREF(sntppkt_resp ));
304+
return 0;
305+
}
306+
307+
// Module function map
308+
LROT_BEGIN(sntppkt)
309+
LROT_FUNCENTRY( make_ts , sntppkt_make_ts )
310+
LROT_FUNCENTRY( proc_pkt , sntppkt_proc_pkt )
311+
LROT_FUNCENTRY( pick_resp, sntppkt_pick_resp )
312+
LROT_FUNCENTRY( read_resp, sntppkt_read_resp )
313+
LROT_END( sntppkt, NULL, 0 )
314+
315+
NODEMCU_MODULE(SNTPPKT, "sntppkt", sntppkt, sntppkt_init);

0 commit comments

Comments
 (0)