Synchronize with trunk.
[reactos.git] / base / applications / network / ping / ping.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS ping utility
4 * FILE: base/applications/network/ping/ping.c
5 * PURPOSE: Network test utility
6 * PROGRAMMERS:
7 */
8
9 #define WIN32_NO_STATUS
10 #include <stdarg.h>
11 #include <windef.h>
12 #include <winbase.h>
13 #include <winuser.h>
14 #include <winnls.h>
15 #include <wincon.h>
16 #define _INC_WINDOWS
17 #include <ws2tcpip.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20
21 #include "resource.h"
22
23 #define NDEBUG
24
25 /* General ICMP constants */
26 #define ICMP_MINSIZE 8 /* Minimum ICMP packet size */
27 #define ICMP_MAXSIZE 65535 /* Maximum ICMP packet size */
28
29 /* ICMP message types */
30 #define ICMPMSG_ECHOREQUEST 8 /* ICMP ECHO request message */
31 #define ICMPMSG_ECHOREPLY 0 /* ICMP ECHO reply message */
32
33 #pragma pack(4)
34
35 /* IPv4 header structure */
36 typedef struct _IPv4_HEADER
37 {
38 unsigned char IHL:4;
39 unsigned char Version:4;
40 unsigned char TOS;
41 unsigned short Length;
42 unsigned short Id;
43 unsigned short FragFlags;
44 unsigned char TTL;
45 unsigned char Protocol;
46 unsigned short Checksum;
47 unsigned int SrcAddress;
48 unsigned int DstAddress;
49 } IPv4_HEADER, *PIPv4_HEADER;
50
51 /* ICMP echo request/reply header structure */
52 typedef struct _ICMP_HEADER
53 {
54 unsigned char Type;
55 unsigned char Code;
56 unsigned short Checksum;
57 unsigned short Id;
58 unsigned short SeqNum;
59 } ICMP_HEADER, *PICMP_HEADER;
60
61 typedef struct _ICMP_ECHO_PACKET
62 {
63 ICMP_HEADER Icmp;
64 } ICMP_ECHO_PACKET, *PICMP_ECHO_PACKET;
65
66 #pragma pack(1)
67
68 BOOL NeverStop;
69 BOOL ResolveAddresses;
70 UINT PingCount;
71 UINT DataSize; /* ICMP echo request data size */
72 BOOL DontFragment;
73 ULONG TTLValue;
74 ULONG TOSValue;
75 ULONG Timeout;
76 WCHAR TargetName[256];
77 SOCKET IcmpSock;
78 SOCKADDR_IN Target;
79 WCHAR TargetIP[16];
80 FD_SET Fds;
81 TIMEVAL Timeval;
82 UINT CurrentSeqNum;
83 UINT SentCount;
84 UINT LostCount;
85 BOOL MinRTTSet;
86 LARGE_INTEGER MinRTT; /* Minimum round trip time in microseconds */
87 LARGE_INTEGER MaxRTT;
88 LARGE_INTEGER SumRTT;
89 LARGE_INTEGER AvgRTT;
90 LARGE_INTEGER TicksPerMs; /* Ticks per millisecond */
91 LARGE_INTEGER TicksPerUs; /* Ticks per microsecond */
92 LARGE_INTEGER SentTime;
93 BOOL UsePerformanceCounter;
94 HANDLE hStdOutput;
95
96 #ifndef NDEBUG
97 /* Display the contents of a buffer */
98 static VOID DisplayBuffer(
99 PVOID Buffer,
100 DWORD Size)
101 {
102 UINT i;
103 PCHAR p;
104
105 printf("Buffer (0x%p) Size (0x%lX).\n", Buffer, Size);
106
107 p = (PCHAR)Buffer;
108 for (i = 0; i < Size; i++)
109 {
110 if (i % 16 == 0)
111 printf("\n");
112 printf("%02X ", (p[i]) & 0xFF);
113 }
114 }
115 #endif /* !NDEBUG */
116
117 LPWSTR
118 MyLoadString(UINT uID)
119 {
120 HRSRC hres;
121 HGLOBAL hResData;
122 WCHAR *pwsz;
123 UINT string_num, i;
124
125 hres = FindResourceW(NULL, MAKEINTRESOURCEW((LOWORD(uID) >> 4) + 1), RT_STRING);
126 if (!hres) return NULL;
127
128 hResData = LoadResource(NULL, hres);
129 if (!hResData) return NULL;
130
131 pwsz = LockResource(hResData);
132 if (!pwsz) return NULL;
133
134 string_num = uID & 15;
135 for (i = 0; i < string_num; i++)
136 pwsz += *pwsz + 1;
137
138 return pwsz + 1;
139 }
140
141 void FormatOutput(UINT uID, ...)
142 {
143 va_list valist;
144
145 WCHAR Buf[1024];
146 LPWSTR pBuf = Buf;
147 LPWSTR Format;
148 DWORD written;
149 UINT DataLength;
150
151 va_start(valist, uID);
152
153 Format = MyLoadString(uID);
154 if (!Format) return;
155
156 DataLength = FormatMessage(FORMAT_MESSAGE_FROM_STRING, Format, 0, 0, Buf,\
157 sizeof(Buf) / sizeof(WCHAR), &valist);
158
159 if(!DataLength)
160 {
161 if(GetLastError() != ERROR_INSUFFICIENT_BUFFER)
162 return;
163
164 DataLength = FormatMessage(FORMAT_MESSAGE_FROM_STRING |\
165 FORMAT_MESSAGE_ALLOCATE_BUFFER,\
166 Format, 0, 0, (LPWSTR)&pBuf, 0, &valist);
167
168 if(!DataLength)
169 return;
170 }
171
172 WriteConsole(hStdOutput, pBuf, DataLength, &written, NULL);
173
174 if(pBuf != Buf)
175 LocalFree(pBuf);
176 }
177
178 /* Display usage information on screen */
179 static VOID Usage(VOID)
180 {
181 FormatOutput(IDS_USAGE);
182 }
183
184 /* Reset configuration to default values */
185 static VOID Reset(VOID)
186 {
187 LARGE_INTEGER PerformanceCounterFrequency;
188
189 NeverStop = FALSE;
190 ResolveAddresses = FALSE;
191 PingCount = 4;
192 DataSize = 32;
193 DontFragment = FALSE;
194 TTLValue = 128;
195 TOSValue = 0;
196 Timeout = 1000;
197 UsePerformanceCounter = QueryPerformanceFrequency(&PerformanceCounterFrequency);
198
199 if (UsePerformanceCounter)
200 {
201 /* Performance counters may return incorrect results on some multiprocessor
202 platforms so we restrict execution on the first processor. This may fail
203 on Windows NT so we fall back to GetCurrentTick() for timing */
204 if (SetThreadAffinityMask (GetCurrentThread(), 1) == 0)
205 UsePerformanceCounter = FALSE;
206
207 /* Convert frequency to ticks per millisecond */
208 TicksPerMs.QuadPart = PerformanceCounterFrequency.QuadPart / 1000;
209 /* And to ticks per microsecond */
210 TicksPerUs.QuadPart = PerformanceCounterFrequency.QuadPart / 1000000;
211 }
212 if (!UsePerformanceCounter)
213 {
214 /* 1 tick per millisecond for GetCurrentTick() */
215 TicksPerMs.QuadPart = 1;
216 /* GetCurrentTick() cannot handle microseconds */
217 TicksPerUs.QuadPart = 1;
218 }
219 }
220
221 /* Parse command line parameters */
222 static BOOL ParseCmdline(int argc, LPWSTR argv[])
223 {
224 INT i;
225 BOOL FoundTarget = FALSE, InvalidOption = FALSE;
226
227 if (argc < 2)
228 {
229 Usage();
230 return FALSE;
231 }
232
233 for (i = 1; i < argc; i++)
234 {
235 if (argv[i][0] == L'-' || argv[i][0] == L'/')
236 {
237 switch (argv[i][1])
238 {
239 case L't': NeverStop = TRUE; break;
240 case L'a': ResolveAddresses = TRUE; break;
241 case L'n':
242 if (i + 1 < argc)
243 PingCount = wcstoul(argv[++i], NULL, 0);
244 else
245 InvalidOption = TRUE;
246 break;
247 case L'l':
248 if (i + 1 < argc)
249 {
250 DataSize = wcstoul(argv[++i], NULL, 0);
251
252 if (DataSize > ICMP_MAXSIZE - sizeof(ICMP_ECHO_PACKET) - sizeof(IPv4_HEADER))
253 {
254 FormatOutput(IDS_BAD_VALUE_OPTION_L, ICMP_MAXSIZE - \
255 (int)sizeof(ICMP_ECHO_PACKET) - \
256 (int)sizeof(IPv4_HEADER));
257 return FALSE;
258 }
259 } else
260 InvalidOption = TRUE;
261 break;
262 case L'f': DontFragment = TRUE; break;
263 case L'i':
264 if (i + 1 < argc)
265 TTLValue = wcstoul(argv[++i], NULL, 0);
266 else
267 InvalidOption = TRUE;
268 break;
269 case L'v':
270 if (i + 1 < argc)
271 TOSValue = wcstoul(argv[++i], NULL, 0);
272 else
273 InvalidOption = TRUE;
274 break;
275 case L'w':
276 if (i + 1 < argc)
277 Timeout = wcstoul(argv[++i], NULL, 0);
278 else
279 InvalidOption = TRUE;
280 break;
281 case '?':
282 Usage();
283 return FALSE;
284 default:
285 FormatOutput(IDS_BAD_OPTION, argv[i]);
286 return FALSE;
287 }
288 if (InvalidOption)
289 {
290 FormatOutput(IDS_BAD_OPTION_FORMAT, argv[i]);
291 return FALSE;
292 }
293 }
294 else
295 {
296 if (FoundTarget)
297 {
298 FormatOutput(IDS_BAD_PARAMETER, argv[i]);
299 return FALSE;
300 }
301 else
302 {
303 wcscpy(TargetName, argv[i]);
304 FoundTarget = TRUE;
305 }
306 }
307 }
308
309 if (!FoundTarget)
310 {
311 FormatOutput(IDS_DEST_MUST_BE_SPECIFIED);
312 return FALSE;
313 }
314
315 return TRUE;
316 }
317
318 /* Calculate checksum of data */
319 static WORD Checksum(PUSHORT data, UINT size)
320 {
321 ULONG sum = 0;
322
323 while (size > 1)
324 {
325 sum += *data++;
326 size -= sizeof(USHORT);
327 }
328
329 if (size)
330 sum += *(UCHAR*)data;
331
332 sum = (sum >> 16) + (sum & 0xFFFF);
333 sum += (sum >> 16);
334
335 return (USHORT)(~sum);
336 }
337
338 /* Prepare to ping target */
339 static BOOL Setup(VOID)
340 {
341 WORD wVersionRequested;
342 WSADATA WsaData;
343 INT Status;
344 ULONG Addr;
345 PHOSTENT phe;
346 CHAR aTargetName[256];
347
348 wVersionRequested = MAKEWORD(2, 2);
349
350 Status = WSAStartup(wVersionRequested, &WsaData);
351 if (Status != 0)
352 {
353 FormatOutput(IDS_COULD_NOT_INIT_WINSOCK);
354 return FALSE;
355 }
356
357 IcmpSock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, 0);
358 if (IcmpSock == INVALID_SOCKET)
359 {
360 FormatOutput(IDS_COULD_NOT_CREATE_SOCKET, WSAGetLastError());
361 return FALSE;
362 }
363
364 if (setsockopt(IcmpSock,
365 IPPROTO_IP,
366 IP_DONTFRAGMENT,
367 (const char *)&DontFragment,
368 sizeof(DontFragment)) == SOCKET_ERROR)
369 {
370 FormatOutput(IDS_SETSOCKOPT_FAILED, WSAGetLastError());
371 return FALSE;
372 }
373
374 if (setsockopt(IcmpSock,
375 IPPROTO_IP,
376 IP_TTL,
377 (const char *)&TTLValue,
378 sizeof(TTLValue)) == SOCKET_ERROR)
379 {
380 FormatOutput(IDS_SETSOCKOPT_FAILED, WSAGetLastError());
381 return FALSE;
382 }
383
384
385 if(!WideCharToMultiByte(CP_ACP, 0, TargetName, -1, aTargetName,\
386 sizeof(aTargetName), NULL, NULL))
387 {
388 FormatOutput(IDS_UNKNOWN_HOST, TargetName);
389 return FALSE;
390 }
391
392 ZeroMemory(&Target, sizeof(Target));
393 phe = NULL;
394 Addr = inet_addr(aTargetName);
395 if (Addr == INADDR_NONE)
396 {
397 phe = gethostbyname(aTargetName);
398 if (phe == NULL)
399 {
400 FormatOutput(IDS_UNKNOWN_HOST, TargetName);
401 return FALSE;
402 }
403
404 CopyMemory(&Target.sin_addr, phe->h_addr, phe->h_length);
405 Target.sin_family = phe->h_addrtype;
406 }
407 else
408 {
409 Target.sin_addr.s_addr = Addr;
410 Target.sin_family = AF_INET;
411 }
412
413
414 swprintf(TargetIP, L"%d.%d.%d.%d", Target.sin_addr.S_un.S_un_b.s_b1,\
415 Target.sin_addr.S_un.S_un_b.s_b2,\
416 Target.sin_addr.S_un.S_un_b.s_b3,\
417 Target.sin_addr.S_un.S_un_b.s_b4);
418 CurrentSeqNum = 1;
419 SentCount = 0;
420 LostCount = 0;
421 MinRTT.QuadPart = 0;
422 MaxRTT.QuadPart = 0;
423 SumRTT.QuadPart = 0;
424 MinRTTSet = FALSE;
425 return TRUE;
426 }
427
428 /* Close socket */
429 static VOID Cleanup(VOID)
430 {
431 if (IcmpSock != INVALID_SOCKET)
432 closesocket(IcmpSock);
433
434 WSACleanup();
435 }
436
437 static VOID QueryTime(PLARGE_INTEGER Time)
438 {
439 if (UsePerformanceCounter)
440 {
441 if (QueryPerformanceCounter(Time) == 0)
442 {
443 /* This should not happen, but we fall
444 back to GetCurrentTick() if it does */
445 Time->u.LowPart = (ULONG)GetTickCount();
446 Time->u.HighPart = 0;
447
448 /* 1 tick per millisecond for GetCurrentTick() */
449 TicksPerMs.QuadPart = 1;
450 /* GetCurrentTick() cannot handle microseconds */
451 TicksPerUs.QuadPart = 1;
452
453 UsePerformanceCounter = FALSE;
454 }
455 }
456 else
457 {
458 Time->u.LowPart = (ULONG)GetTickCount();
459 Time->u.HighPart = 0;
460 }
461 }
462
463 static VOID TimeToMsString(LPWSTR String, LARGE_INTEGER Time)
464 {
465 WCHAR Convstr[40];
466 LARGE_INTEGER LargeTime;
467 LPWSTR ms;
468
469 LargeTime.QuadPart = Time.QuadPart / TicksPerMs.QuadPart;
470
471 _i64tow(LargeTime.QuadPart, Convstr, 10);
472 wcscpy(String, Convstr);
473 ms = MyLoadString(IDS_MS);
474 wcscat(String, ms);
475 }
476
477 /* Locate the ICMP data and print it. Returns TRUE if the packet was good,
478 FALSE if not */
479 static BOOL DecodeResponse(PCHAR buffer, UINT size, PSOCKADDR_IN from)
480 {
481 PIPv4_HEADER IpHeader;
482 PICMP_ECHO_PACKET Icmp;
483 UINT IphLength;
484 WCHAR Time[100];
485 LARGE_INTEGER RelativeTime;
486 LARGE_INTEGER LargeTime;
487 WCHAR Sign[2];
488 WCHAR wfromIP[16];
489
490 IpHeader = (PIPv4_HEADER)buffer;
491
492 IphLength = IpHeader->IHL * 4;
493
494 if (size < IphLength + ICMP_MINSIZE)
495 {
496 #ifndef NDEBUG
497 printf("Bad size (0x%X < 0x%X)\n", size, IphLength + ICMP_MINSIZE);
498 #endif /* !NDEBUG */
499 return FALSE;
500 }
501
502 Icmp = (PICMP_ECHO_PACKET)(buffer + IphLength);
503
504 if (Icmp->Icmp.Type != ICMPMSG_ECHOREPLY)
505 {
506 #ifndef NDEBUG
507 printf("Bad ICMP type (0x%X should be 0x%X)\n", Icmp->Icmp.Type, ICMPMSG_ECHOREPLY);
508 #endif /* !NDEBUG */
509 return FALSE;
510 }
511
512 if (Icmp->Icmp.Id != (USHORT)GetCurrentProcessId())
513 {
514 #ifndef NDEBUG
515 printf("Bad ICMP id (0x%X should be 0x%X)\n", Icmp->Icmp.Id, (USHORT)GetCurrentProcessId());
516 #endif /* !NDEBUG */
517 return FALSE;
518 }
519
520 if (from->sin_addr.s_addr != Target.sin_addr.s_addr)
521 {
522 #ifndef NDEBUG
523 printf("Bad source address (%s should be %s)\n", inet_ntoa(from->sin_addr), inet_ntoa(Target.sin_addr));
524 #endif /* !NDEBUG */
525 return FALSE;
526 }
527
528 QueryTime(&LargeTime);
529
530 RelativeTime.QuadPart = (LargeTime.QuadPart - SentTime.QuadPart);
531
532 if ((RelativeTime.QuadPart / TicksPerMs.QuadPart) < 1)
533 {
534 LPWSTR ms1;
535
536 wcscpy(Sign, L"<");
537 ms1 = MyLoadString(IDS_1MS);
538 wcscpy(Time, ms1);
539 }
540 else
541 {
542 wcscpy(Sign, L"=");
543 TimeToMsString(Time, RelativeTime);
544 }
545
546
547 swprintf(wfromIP, L"%d.%d.%d.%d", from->sin_addr.S_un.S_un_b.s_b1,\
548 from->sin_addr.S_un.S_un_b.s_b2,\
549 from->sin_addr.S_un.S_un_b.s_b3,\
550 from->sin_addr.S_un.S_un_b.s_b4);
551 FormatOutput(IDS_REPLY_FROM, wfromIP,\
552 size - IphLength - (int)sizeof(ICMP_ECHO_PACKET),\
553 Sign, Time, IpHeader->TTL);
554
555 if (RelativeTime.QuadPart < MinRTT.QuadPart || !MinRTTSet)
556 {
557 MinRTT.QuadPart = RelativeTime.QuadPart;
558 MinRTTSet = TRUE;
559 }
560 if (RelativeTime.QuadPart > MaxRTT.QuadPart)
561 MaxRTT.QuadPart = RelativeTime.QuadPart;
562
563 SumRTT.QuadPart += RelativeTime.QuadPart;
564
565 return TRUE;
566 }
567
568 /* Send and receive one ping */
569 static BOOL Ping(VOID)
570 {
571 INT Status;
572 SOCKADDR From;
573 INT Length;
574 PVOID Buffer;
575 UINT Size;
576 PICMP_ECHO_PACKET Packet;
577
578 /* Account for extra space for IP header when packet is received */
579 Size = DataSize + 128;
580 Buffer = GlobalAlloc(0, Size);
581 if (!Buffer)
582 {
583 FormatOutput(IDS_NOT_ENOUGH_RESOURCES);
584 return FALSE;
585 }
586
587 ZeroMemory(Buffer, Size);
588 Packet = (PICMP_ECHO_PACKET)Buffer;
589
590 /* Assemble ICMP echo request packet */
591 Packet->Icmp.Type = ICMPMSG_ECHOREQUEST;
592 Packet->Icmp.Code = 0;
593 Packet->Icmp.Id = (USHORT)GetCurrentProcessId();
594 Packet->Icmp.SeqNum = htons((USHORT)CurrentSeqNum);
595 Packet->Icmp.Checksum = 0;
596
597 /* Calculate checksum for ICMP header and data area */
598 Packet->Icmp.Checksum = Checksum((PUSHORT)&Packet->Icmp, sizeof(ICMP_ECHO_PACKET) + DataSize);
599
600 CurrentSeqNum++;
601
602 /* Send ICMP echo request */
603
604 FD_ZERO(&Fds);
605 FD_SET(IcmpSock, &Fds);
606 Timeval.tv_sec = Timeout / 1000;
607 Timeval.tv_usec = Timeout % 1000;
608 Status = select(0, NULL, &Fds, NULL, &Timeval);
609 if ((Status != SOCKET_ERROR) && (Status != 0))
610 {
611
612 #ifndef NDEBUG
613 printf("Sending packet\n");
614 DisplayBuffer(Buffer, sizeof(ICMP_ECHO_PACKET) + DataSize);
615 printf("\n");
616 #endif /* !NDEBUG */
617
618 Status = sendto(IcmpSock, Buffer, sizeof(ICMP_ECHO_PACKET) + DataSize,
619 0, (SOCKADDR*)&Target, sizeof(Target));
620 QueryTime(&SentTime);
621 SentCount++;
622 }
623 if (Status == SOCKET_ERROR)
624 {
625 if (WSAGetLastError() == WSAEHOSTUNREACH)
626 FormatOutput(IDS_DEST_UNREACHABLE);
627 else
628 FormatOutput(IDS_COULD_NOT_TRANSMIT, WSAGetLastError());
629 GlobalFree(Buffer);
630 return FALSE;
631 }
632
633 /* Expect to receive ICMP echo reply */
634 FD_ZERO(&Fds);
635 FD_SET(IcmpSock, &Fds);
636 Timeval.tv_sec = Timeout / 1000;
637 Timeval.tv_usec = Timeout % 1000;
638
639 do {
640 Status = select(0, &Fds, NULL, NULL, &Timeval);
641 if ((Status != SOCKET_ERROR) && (Status != 0))
642 {
643 Length = sizeof(From);
644 Status = recvfrom(IcmpSock, Buffer, Size, 0, &From, &Length);
645
646 #ifndef NDEBUG
647 printf("Received packet\n");
648 DisplayBuffer(Buffer, Status);
649 printf("\n");
650 #endif /* !NDEBUG */
651 }
652 else
653 LostCount++;
654 if (Status == SOCKET_ERROR)
655 {
656 if (WSAGetLastError() != WSAETIMEDOUT)
657 {
658 FormatOutput(IDS_COULD_NOT_RECV, WSAGetLastError());
659 GlobalFree(Buffer);
660 return FALSE;
661 }
662 Status = 0;
663 }
664
665 if (Status == 0)
666 {
667 FormatOutput(IDS_REQUEST_TIMEOUT);
668 GlobalFree(Buffer);
669 return TRUE;
670 }
671
672 } while (!DecodeResponse(Buffer, Status, (PSOCKADDR_IN)&From));
673
674 GlobalFree(Buffer);
675 return TRUE;
676 }
677
678
679 /* Program entry point */
680 int wmain(int argc, LPWSTR argv[])
681 {
682 UINT Count;
683 WCHAR MinTime[20];
684 WCHAR MaxTime[20];
685 WCHAR AvgTime[20];
686
687 hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
688
689 Reset();
690
691 if ((ParseCmdline(argc, argv)) && (Setup()))
692 {
693
694 FormatOutput(IDS_PING_WITH_BYTES, TargetName, TargetIP, DataSize);
695
696 Count = 0;
697 while ((NeverStop) || (Count < PingCount))
698 {
699 Ping();
700 Count++;
701 if((NeverStop) || (Count < PingCount))
702 Sleep(Timeout);
703 };
704
705 Cleanup();
706
707 /* Calculate avarage round trip time */
708 if ((SentCount - LostCount) > 0)
709 AvgRTT.QuadPart = SumRTT.QuadPart / (SentCount - LostCount);
710 else
711 AvgRTT.QuadPart = 0;
712
713 /* Calculate loss percent */
714 Count = SentCount ? (LostCount * 100) / SentCount : 0;
715
716 if (!MinRTTSet)
717 MinRTT = MaxRTT;
718
719 TimeToMsString(MinTime, MinRTT);
720 TimeToMsString(MaxTime, MaxRTT);
721 TimeToMsString(AvgTime, AvgRTT);
722
723 /* Print statistics */
724 FormatOutput(IDS_PING_STATISTICS, TargetIP);
725 FormatOutput(IDS_PACKETS_SENT_RECEIVED_LOST,\
726 SentCount, SentCount - LostCount, LostCount, Count);
727
728
729 /* Print approximate times or NO approximate times if 100% loss */
730 if ((SentCount - LostCount) > 0)
731 {
732 FormatOutput(IDS_APPROXIMATE_ROUND_TRIP);
733 FormatOutput(IDS_MIN_MAX_AVERAGE, MinTime, MaxTime, AvgTime);
734 }
735 }
736 else
737 {
738 return 1;
739 }
740 return 0;
741 }
742
743 /* EOF */