sntpsend.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. {==============================================================================|
  2. | Project : Ararat Synapse | 003.000.002 |
  3. |==============================================================================|
  4. | Content: SNTP client |
  5. |==============================================================================|
  6. | Copyright (c)1999-2007, Lukas Gebauer |
  7. | All rights reserved. |
  8. | |
  9. | Redistribution and use in source and binary forms, with or without |
  10. | modification, are permitted provided that the following conditions are met: |
  11. | |
  12. | Redistributions of source code must retain the above copyright notice, this |
  13. | list of conditions and the following disclaimer. |
  14. | |
  15. | Redistributions in binary form must reproduce the above copyright notice, |
  16. | this list of conditions and the following disclaimer in the documentation |
  17. | and/or other materials provided with the distribution. |
  18. | |
  19. | Neither the name of Lukas Gebauer nor the names of its contributors may |
  20. | be used to endorse or promote products derived from this software without |
  21. | specific prior written permission. |
  22. | |
  23. | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
  24. | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
  25. | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
  26. | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR |
  27. | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
  28. | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
  29. | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
  30. | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
  31. | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
  32. | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
  33. | DAMAGE. |
  34. |==============================================================================|
  35. | The Initial Developer of the Original Code is Lukas Gebauer (Czech Republic).|
  36. | Portions created by Lukas Gebauer are Copyright (c)2000-2007. |
  37. | All Rights Reserved. |
  38. |==============================================================================|
  39. | Contributor(s): |
  40. | Patrick Chevalley |
  41. |==============================================================================|
  42. | History: see HISTORY.HTM from distribution package |
  43. | (Found at URL: http://www.ararat.cz/synapse/) |
  44. |==============================================================================}
  45. {:@abstract( NTP and SNTP client)
  46. Used RFC: RFC-1305, RFC-2030
  47. }
  48. {$IFDEF FPC}
  49. {$MODE DELPHI}
  50. {$ENDIF}
  51. {$Q-}
  52. {$H+}
  53. unit sntpsend;
  54. interface
  55. uses
  56. SysUtils,
  57. synsock, blcksock, synautil;
  58. const
  59. cNtpProtocol = '123';
  60. type
  61. {:@abstract(Record containing the NTP packet.)}
  62. TNtp = packed record
  63. mode: Byte;
  64. stratum: Byte;
  65. poll: Byte;
  66. Precision: Byte;
  67. RootDelay: Longint;
  68. RootDisperson: Longint;
  69. RefID: Longint;
  70. Ref1: Longint;
  71. Ref2: Longint;
  72. Org1: Longint;
  73. Org2: Longint;
  74. Rcv1: Longint;
  75. Rcv2: Longint;
  76. Xmit1: Longint;
  77. Xmit2: Longint;
  78. end;
  79. {:@abstract(Implementation of NTP and SNTP client protocol),
  80. include time synchronisation. It can send NTP or SNTP time queries, or it
  81. can receive NTP broadcasts too.
  82. Note: Are you missing properties for specify server address and port? Look to
  83. parent @link(TSynaClient) too!}
  84. TSNTPSend = class(TSynaClient)
  85. private
  86. FNTPReply: TNtp;
  87. FNTPTime: TDateTime;
  88. FNTPOffset: double;
  89. FNTPDelay: double;
  90. FMaxSyncDiff: double;
  91. FSyncTime: Boolean;
  92. FSock: TUDPBlockSocket;
  93. FBuffer: string;
  94. FLi, FVn, Fmode : byte;
  95. function StrToNTP(const Value: AnsiString): TNtp;
  96. function NTPtoStr(const Value: Tntp): AnsiString;
  97. procedure ClearNTP(var Value: Tntp);
  98. public
  99. constructor Create;
  100. destructor Destroy; override;
  101. {:Decode 128 bit timestamp used in NTP packet to TDateTime type.}
  102. function DecodeTs(Nsec, Nfrac: Longint): TDateTime;
  103. {:Decode TDateTime type to 128 bit timestamp used in NTP packet.}
  104. procedure EncodeTs(dt: TDateTime; var Nsec, Nfrac: Longint);
  105. {:Send request to @link(TSynaClient.TargetHost) and wait for reply. If all
  106. is OK, then result is @true and @link(NTPReply) and @link(NTPTime) are
  107. valid.}
  108. function GetSNTP: Boolean;
  109. {:Send request to @link(TSynaClient.TargetHost) and wait for reply. If all
  110. is OK, then result is @true and @link(NTPReply) and @link(NTPTime) are
  111. valid. Result time is after all needed corrections.}
  112. function GetNTP: Boolean;
  113. {:Wait for broadcast NTP packet. If all OK, result is @true and
  114. @link(NTPReply) and @link(NTPTime) are valid.}
  115. function GetBroadcastNTP: Boolean;
  116. {:Holds last received NTP packet.}
  117. property NTPReply: TNtp read FNTPReply;
  118. published
  119. {:Date and time of remote NTP or SNTP server. (UTC time!!!)}
  120. property NTPTime: TDateTime read FNTPTime;
  121. {:Offset between your computer and remote NTP or SNTP server.}
  122. property NTPOffset: Double read FNTPOffset;
  123. {:Delay between your computer and remote NTP or SNTP server.}
  124. property NTPDelay: Double read FNTPDelay;
  125. {:Define allowed maximum difference between your time and remote time for
  126. synchronising time. If difference is bigger, your system time is not
  127. changed!}
  128. property MaxSyncDiff: double read FMaxSyncDiff write FMaxSyncDiff;
  129. {:If @true, after successfull getting time is local computer clock
  130. synchronised to given time.
  131. For synchronising time you must have proper rights! (Usually Administrator)}
  132. property SyncTime: Boolean read FSyncTime write FSyncTime;
  133. {:Socket object used for TCP/IP operation. Good for seting OnStatus hook, etc.}
  134. property Sock: TUDPBlockSocket read FSock;
  135. end;
  136. implementation
  137. constructor TSNTPSend.Create;
  138. begin
  139. inherited Create;
  140. FSock := TUDPBlockSocket.Create;
  141. FTimeout := 5000;
  142. FTargetPort := cNtpProtocol;
  143. FMaxSyncDiff := 3600;
  144. FSyncTime := False;
  145. end;
  146. destructor TSNTPSend.Destroy;
  147. begin
  148. FSock.Free;
  149. inherited Destroy;
  150. end;
  151. function TSNTPSend.StrToNTP(const Value: AnsiString): TNtp;
  152. begin
  153. if length(FBuffer) >= SizeOf(Result) then
  154. begin
  155. Result.mode := ord(Value[1]);
  156. Result.stratum := ord(Value[2]);
  157. Result.poll := ord(Value[3]);
  158. Result.Precision := ord(Value[4]);
  159. Result.RootDelay := DecodeLongInt(value, 5);
  160. Result.RootDisperson := DecodeLongInt(value, 9);
  161. Result.RefID := DecodeLongInt(value, 13);
  162. Result.Ref1 := DecodeLongInt(value, 17);
  163. Result.Ref2 := DecodeLongInt(value, 21);
  164. Result.Org1 := DecodeLongInt(value, 25);
  165. Result.Org2 := DecodeLongInt(value, 29);
  166. Result.Rcv1 := DecodeLongInt(value, 33);
  167. Result.Rcv2 := DecodeLongInt(value, 37);
  168. Result.Xmit1 := DecodeLongInt(value, 41);
  169. Result.Xmit2 := DecodeLongInt(value, 45);
  170. end;
  171. end;
  172. function TSNTPSend.NTPtoStr(const Value: Tntp): AnsiString;
  173. begin
  174. SetLength(Result, 4);
  175. Result[1] := AnsiChar(Value.mode);
  176. Result[2] := AnsiChar(Value.stratum);
  177. Result[3] := AnsiChar(Value.poll);
  178. Result[4] := AnsiChar(Value.precision);
  179. Result := Result + CodeLongInt(Value.RootDelay);
  180. Result := Result + CodeLongInt(Value.RootDisperson);
  181. Result := Result + CodeLongInt(Value.RefID);
  182. Result := Result + CodeLongInt(Value.Ref1);
  183. Result := Result + CodeLongInt(Value.Ref2);
  184. Result := Result + CodeLongInt(Value.Org1);
  185. Result := Result + CodeLongInt(Value.Org2);
  186. Result := Result + CodeLongInt(Value.Rcv1);
  187. Result := Result + CodeLongInt(Value.Rcv2);
  188. Result := Result + CodeLongInt(Value.Xmit1);
  189. Result := Result + CodeLongInt(Value.Xmit2);
  190. end;
  191. procedure TSNTPSend.ClearNTP(var Value: Tntp);
  192. begin
  193. Value.mode := 0;
  194. Value.stratum := 0;
  195. Value.poll := 0;
  196. Value.Precision := 0;
  197. Value.RootDelay := 0;
  198. Value.RootDisperson := 0;
  199. Value.RefID := 0;
  200. Value.Ref1 := 0;
  201. Value.Ref2 := 0;
  202. Value.Org1 := 0;
  203. Value.Org2 := 0;
  204. Value.Rcv1 := 0;
  205. Value.Rcv2 := 0;
  206. Value.Xmit1 := 0;
  207. Value.Xmit2 := 0;
  208. end;
  209. function TSNTPSend.DecodeTs(Nsec, Nfrac: Longint): TDateTime;
  210. const
  211. maxi = 4294967295.0;
  212. var
  213. d, d1: Double;
  214. begin
  215. d := Nsec;
  216. if d < 0 then
  217. d := maxi + d + 1;
  218. d1 := Nfrac;
  219. if d1 < 0 then
  220. d1 := maxi + d1 + 1;
  221. d1 := d1 / maxi;
  222. d1 := Trunc(d1 * 10000) / 10000;
  223. Result := (d + d1) / 86400;
  224. Result := Result + 2;
  225. end;
  226. procedure TSNTPSend.EncodeTs(dt: TDateTime; var Nsec, Nfrac: Longint);
  227. const
  228. maxi = 4294967295.0;
  229. maxilongint = 2147483647;
  230. var
  231. d, d1: Double;
  232. begin
  233. d := (dt - 2) * 86400;
  234. d1 := frac(d);
  235. if d > maxilongint then
  236. d := d - maxi - 1;
  237. d := trunc(d);
  238. d1 := Trunc(d1 * 10000) / 10000;
  239. d1 := d1 * maxi;
  240. if d1 > maxilongint then
  241. d1 := d1 - maxi - 1;
  242. Nsec:=trunc(d);
  243. Nfrac:=trunc(d1);
  244. end;
  245. function TSNTPSend.GetBroadcastNTP: Boolean;
  246. var
  247. x: Integer;
  248. begin
  249. Result := False;
  250. FSock.Bind(FIPInterface, FTargetPort);
  251. FBuffer := FSock.RecvPacket(FTimeout);
  252. if FSock.LastError = 0 then
  253. begin
  254. x := Length(FBuffer);
  255. if (FTargetHost = '0.0.0.0') or (FSock.GetRemoteSinIP = FSock.ResolveName(FTargetHost)) then
  256. if x >= SizeOf(NTPReply) then
  257. begin
  258. FNTPReply := StrToNTP(FBuffer);
  259. FNTPTime := DecodeTs(NTPReply.Xmit1, NTPReply.Xmit2);
  260. if FSyncTime and ((abs(FNTPTime - GetUTTime) * 86400) <= FMaxSyncDiff) then
  261. SetUTTime(FNTPTime);
  262. Result := True;
  263. end;
  264. end;
  265. end;
  266. function TSNTPSend.GetSNTP: Boolean;
  267. var
  268. q: TNtp;
  269. x: Integer;
  270. begin
  271. Result := False;
  272. FSock.CloseSocket;
  273. FSock.Bind(FIPInterface, cAnyPort);
  274. FSock.Connect(FTargetHost, FTargetPort);
  275. ClearNtp(q);
  276. q.mode := $1B;
  277. FBuffer := NTPtoStr(q);
  278. FSock.SendString(FBuffer);
  279. FBuffer := FSock.RecvPacket(FTimeout);
  280. if FSock.LastError = 0 then
  281. begin
  282. x := Length(FBuffer);
  283. if x >= SizeOf(NTPReply) then
  284. begin
  285. FNTPReply := StrToNTP(FBuffer);
  286. FNTPTime := DecodeTs(NTPReply.Xmit1, NTPReply.Xmit2);
  287. if FSyncTime and ((abs(FNTPTime - GetUTTime) * 86400) <= FMaxSyncDiff) then
  288. SetUTTime(FNTPTime);
  289. Result := True;
  290. end;
  291. end;
  292. end;
  293. function TSNTPSend.GetNTP: Boolean;
  294. var
  295. q: TNtp;
  296. x: Integer;
  297. t1, t2, t3, t4 : TDateTime;
  298. begin
  299. Result := False;
  300. FSock.CloseSocket;
  301. FSock.Bind(FIPInterface, cAnyPort);
  302. FSock.Connect(FTargetHost, FTargetPort);
  303. ClearNtp(q);
  304. q.mode := $1B;
  305. t1 := GetUTTime;
  306. EncodeTs(t1, q.org1, q.org2);
  307. FBuffer := NTPtoStr(q);
  308. FSock.SendString(FBuffer);
  309. FBuffer := FSock.RecvPacket(FTimeout);
  310. if FSock.LastError = 0 then
  311. begin
  312. x := Length(FBuffer);
  313. t4 := GetUTTime;
  314. if x >= SizeOf(NTPReply) then
  315. begin
  316. FNTPReply := StrToNTP(FBuffer);
  317. FLi := (NTPReply.mode and $C0) shr 6;
  318. FVn := (NTPReply.mode and $38) shr 3;
  319. Fmode := NTPReply.mode and $07;
  320. if (Fli < 3) and (Fmode = 4) and
  321. (NTPReply.stratum >= 1) and (NTPReply.stratum <= 15) and
  322. (NTPReply.Rcv1 <> 0) and (NTPReply.Xmit1 <> 0)
  323. then begin
  324. t2 := DecodeTs(NTPReply.Rcv1, NTPReply.Rcv2);
  325. t3 := DecodeTs(NTPReply.Xmit1, NTPReply.Xmit2);
  326. FNTPDelay := (T4 - T1) - (T2 - T3);
  327. FNTPTime := t3 + FNTPDelay / 2;
  328. FNTPOffset := (((T2 - T1) + (T3 - T4)) / 2) * 86400;
  329. FNTPDelay := FNTPDelay * 86400;
  330. if FSyncTime and ((abs(FNTPTime - t1) * 86400) <= FMaxSyncDiff) then
  331. SetUTTime(FNTPTime);
  332. Result := True;
  333. end
  334. else result:=false;
  335. end;
  336. end;
  337. end;
  338. end.