Skip to content

Commit 939fba5

Browse files
committed
large upgrade
1 parent b83882c commit 939fba5

36 files changed

Lines changed: 3634 additions & 673 deletions

README.md

Lines changed: 266 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,266 @@
1-
# NTP Client (Windows Dll/Lib + console App)
2-
TBA
3-
![Console](https://raw.githubusercontent.com/parezj/NTP-Client/master/img/ntp_client_console.png)
4-
TBA
1+
# NTP Client (Windows DLL/LIB + Console App + CVI GUI App)
2+
3+
1. [Time Synchronization via NTP](#1-Time-Synchronization-via-NTP)
4+
2. [NTP Client Library (C++ DLL)](#2-NTP-Client---Library-C++-DLL)
5+
2.1 [Description](#Description)
6+
2.2 [C++ interface](#C++-interface)
7+
2.3 [C interface](#C-interface)
8+
2.4 [Example of Use](#Example-of-Use)
9+
3. [NTP client - Graphical Interface (CVI)](#3-NTP-client---Graphical-Interface-CVI)
10+
11+
## 1. Time Synchronization via NTP
12+
13+
NTP has been used to synchronize time in variable response networks since
14+
1985 and that makes it one of the oldest Internet protocols. Uses UDP
15+
OSI layer 4 protocol and port 123. By default, it achieves an accuracy of 10 ms to 200
16+
µs, depending on the quality of the connection.
17+
18+
NTP uses a hierarchical system called "*stratum*". Server of type *stratum* 0
19+
obtains the most accurate time, for example, from a cesium clock, but is not intended for
20+
time distribution to the network. This is done by the server of type *stratum* 1, which it receives
21+
time from *loss* 0. Then there are servers *stratum* 2 to 15, which always
22+
they get the time from the parent server and their number basically shows
23+
distance from the reference clock.
24+
25+
The NTP algorithm begins by sending a defined packet (RFC 5905), respectively
26+
datagram, from client to server. The most important information transmitted by this packet
27+
are client mode (NTPv4), *stratum* local clock, accuracy of local clock,
28+
and especially the time **T1**, which indicates the time of the local clock at the time the packet leaves to
29+
networks. After the NTP server receives the packet, the server writes the time **T2** to it, which
30+
indicates the current time on the server clock and just before sending the time **T3**, which
31+
indicates the time the packet leaves back to the network. After receiving the packet by the client, it is finally
32+
writes the last time **T4**, which indicates the arrival back to the client. if they are
33+
these times are measured accurately, it is enough to calculate the two resulting ones thanks to the formulas below
34+
values. **Offset**, which symbolizes the shift of the client clock from the clock on the server and
35+
**Delay**, which represents the delay of the packet passing through the network, which can be due
36+
switches and network technologies are highly variable. The sum of these values then
37+
represents the final shift of the local clock, which should ideally be
38+
equal to zero.
39+
40+
$$
41+
Offset \ = \ \ frac {\ left (T2 \ - \ T1 \ right) \ + \ \ left (T3 \ - \ T4 \ right)} {2}
42+
$$
43+
44+
$$
45+
Delay \ = \ \ left (T4 \ - \ T1 \ right) \ + \ \ left (T3 \ - \ T2 \ right)
46+
$$
47+
48+
$$
49+
Delta \ = \ Offset \ + \ Delay \
50+
$$
51+
52+
## 2. NTP Client - Library (C++ DLL)
53+
54+
### 2.1 Description
55+
56+
I developed a simple and single-purpose library in C ++ in the environment
57+
Microsoft Visual Studio 2019. I only relied on the official RFC specification
58+
5905. The library is currently designed for Windows NT because it uses Win32
59+
API for reading and writing system time and *Winsock* for UDP communication. However
60+
in the future it is not a problem to extend it with directives \ #ifdef eg with POSIX
61+
*sockets*.
62+
63+
Because the library contains only one **Client** class, the class diagram is
64+
unnecessary.
65+
66+
´´´cpp
67+
class Client: public IClient
68+
´´´
69+
70+
The library has only two public methods, **query** and
71+
**query_and_sync**.
72+
73+
´´´cpp
74+
virtual Status query (const char* hostname, ResultEx** result_out);
75+
virtual Status query_and_sync (const char* hostname, ResultEx** result_out);
76+
´´´
77+
78+
Query is the core of the whole library. At the beginning of this method, a UDP is created first
79+
packet, it is filled with the current values ??I mentioned in the first chapter and
80+
sends it to the NTP server. Upon arrival, he completes the time T4 and performs the calculation, according to
81+
formulas from the first chapter. Times are represented by the time_point class from the library
82+
std :: chrono with resolution to nanoseconds (time **t1**) or class
83+
high_resolution_clock (time **t1_b**).
84+
85+
´´´cpp
86+
typedef std::chrono::time_point<std::chrono::system_clock,
87+
std::chrono::nanoseconds> time_point_t;
88+
time_point_t t1 = std::chrono::time_point_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now());
89+
auto t1_b = std::chrono::high_resolution_clock::now();
90+
´´´
91+
92+
This combination is because the times in the first formula (offset) must be
93+
absolute. These are the times **T2** and **T3** that came from the server. therefore not
94+
use high_resolution_clock, in the second formula (delay) it is then possible to read **T1**
95+
from **T4** use relative times, which can be obtained using high_resolution_clock.
96+
The following formulas show the calculation using this approach, all units
97+
variables are nanoseconds.
98+
99+
´´´cpp
100+
offset = [(T2 - T1) + (T3 - T4)] / 2
101+
delay = (**T4b** - **T1b**) - (T3 - T2)
102+
´´´
103+
104+
By summing the values *offset* and *delay* we get *delta*, ie the value by which
105+
adjust the local system clock. However, this only applies if the latter is used
106+
public methods **query_and_sync**, the first mentioned will only communicate with
107+
server and calculation.
108+
109+
Resulting calculated and obtained values, including *jitter* (stability indicators
110+
network connection) is returned to the user either in the Result structure that is used for
111+
classic C interface, or in the class ResultEx, which, unlike the first contains
112+
time represented by class time_point_t, as opposed to time represented by classical
113+
TimePt structure with *integers*.
114+
115+
´´´cpp
116+
struct Result
117+
{
118+
struct TimePt time;
119+
struct Metrics mtr;
120+
};
121+
´´´
122+
´´´cpp
123+
class ResultEx
124+
{
125+
public:
126+
time_point_t time;
127+
Metrics mtr;
128+
};
129+
´´´
130+
131+
This achieves the compatibility between C and C ++, which is required for a dynamic library.
132+
If the user uses the library directly from C ++, it is more convenient to work with time
133+
represented by the time_point_t class, otherwise there is no choice but to use it
134+
structure.
135+
136+
´´´cpp
137+
struct TimePt
138+
{
139+
int tm_nsec;
140+
int tm_usec;
141+
int tm_msec;
142+
int tm_sec;
143+
int tm_min;
144+
int tm_hour
145+
int tm_mday;
146+
int tm_mon;
147+
int tm_year;
148+
};
149+
´´´
150+
´´´cpp
151+
struct Metrics
152+
{
153+
double delay_ns;
154+
double offset_ns;
155+
double jitter_ns;
156+
double delta_ns;
157+
};
158+
´´´
159+
160+
Error states are returned as *enumerator* Status, where 0 means success
161+
(similar to POSIX) and anything else is a bug.
162+
163+
´´´cpp
164+
enum Status : int16_t
165+
{
166+
OK = 0,
167+
UNKNOWN_ERR = 1,
168+
INIT_WINSOCK_ERR = 2,
169+
CREATE_SOCKET_ERR = 3,
170+
SEND_MSG_ERR = 4,
171+
RECEIVE_MSG_ERR = 5,
172+
RECEIVE_MSG_TIMEOUT = 6,
173+
SET_WIN_TIME_ERR = 7,
174+
ADMIN_RIGHTS_NEEDED = 8
175+
};
176+
´´´
177+
178+
In addition, the library contains several static stateless methods to facilitate the work
179+
programmer, used primarily to format results and convert types.
180+
181+
### 2.2 C++ interface
182+
183+
The standard library interface for use with object-oriented languages is in
184+
form *interface*, which exposes the two main public methods described above
185+
**query** a **query_and_sync**. The interface is only a macro for the struct type,
186+
of course you could use a proprietary MS **__interface**, but most of the time it gets better
187+
stick to proven and compatible things.
188+
189+
´´´cpp
190+
Interface IClient
191+
{
192+
virtual Status query(const char* hostname, ResultEx** result_out) = 0;
193+
virtual Status query_and_sync(const char* hostname, ResultEx**result_out) = 0;
194+
virtual ~IClient() {};
195+
};
196+
´´´
197+
198+
### 2.3 C interface
199+
200+
The interface usable for DLL calls must be compatible with classic ANSI C,
201+
instead of classes, it is necessary to use the classic C OOP style, namely functions, structures and
202+
*opaque pointers*. These functions must then be exported using the EXPORT macro,
203+
which is a macro for **__declspec (dllexport)**. It is also necessary to set adequate
204+
calling convention, in our case it is **__cdecl**, where the one calling as well
205+
cleans the tray.
206+
207+
The **Client__create** function creates a library instance, which is represented
208+
pointer, or macro, HNTP, which in the context of Windows is called
209+
*handle*.
210+
211+
´´´cpp
212+
typedef void* HNTP;
213+
´´´
214+
215+
Other functions, such as **Client__query** or **Client__query_and_sync**
216+
they take this indicator as the first argument. The rest is very similar to C++
217+
interface, however one difference it has. Instead of delete, it must be called at the end
218+
**Client__free_result** and **Client__close**.
219+
220+
´´´cpp
221+
extern "C"
222+
{
223+
/* object lifecycle */
224+
EXPORT HNTP __cdecl Client__create(void);
225+
EXPORT void __cdecl Client__close(HNTP self);
226+
227+
/* main NTP server query functions */
228+
EXPORT enum Status __cdecl Client__query(HNTP self, const char* hostname, struct Result** result_out);
229+
EXPORT enum Status __cdecl Client__query_and_sync(HNTP self, const char* hostname, struct Result** result_out);
230+
231+
/* helper functions */
232+
EXPORT void __cdecl Client__format_info_str(struct Result* result, char* str_out);
233+
EXPORT void __cdecl Client__get_status_str(enum Status status, char* str_out);
234+
EXPORT void __cdecl Client__free_result(struct Result* result);
235+
}
236+
´´´
237+
238+
### 2.4 Example of Use
239+
240+
You must have *runtime* **vc_redist** (2015-19) installed to run. Code
241+
it is at least partially annotated and perhaps even clear. I tried to make it
242+
use trivial. A client instance is created, the *query* function is called, and it terminates
243+
the client. This can be done in an infinite loop with a defined interval,
244+
to ensure constant time synchronization. The following lines are excluded
245+
from a console application that serves as an example of use.
246+
247+
´´´cpp
248+
enum Status s;
249+
struct Result* result = nullptr;
250+
HNTP client = Client__create()
251+
s = Client__query_and_sync(client, "195.113.144.201", &result);
252+
Client__free_result(result);
253+
Client__close(client);
254+
´´´
255+
256+
![Console](https://raw.githubusercontent.com/parezj/NTP-Client/master/img/ntp_client_console2.png)
257+
258+
## 3. NTP client - Graphical Interface (CVI)
259+
260+
I used the dynamic library in the LabWindows / CVI environment to create
261+
graphical interface of the NTP client, which is periodically called from its own thread. On
262+
graph we then see the green delta value (the current difference of the local clock from
263+
server), its diameter in yellow and *jitter* in network communication in red. To run
264+
**CVI Runtime 2019** is required.
265+
266+
![CVI GUI](https://raw.githubusercontent.com/parezj/NTP-Client/master/img/ntp_client_gui.png)

build/ntp_client.dll

18.5 KB
Binary file not shown.

build/ntp_client_console.exe

19.5 KB
Binary file not shown.

build/ntp_client_gui.exe

640 KB
Binary file not shown.

doc/VIN_NTP_klient.pdf

1.28 MB
Binary file not shown.

img/ntp_client_console.png

87.1 KB
Loading

img/ntp_client_console2.png

61.9 KB
Loading

img/ntp_client_gui.png

71.7 KB
Loading

lib/vcredist.png

10.8 KB
Loading

lib/vcredist_x64.exe

-14.3 MB
Binary file not shown.

0 commit comments

Comments
 (0)