1 /*
2  * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
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  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  * 3. The name of the author may not be used to endorse or promote products
13  *    derived from this software without specific prior written permission.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include "event2/event-config.h"
28 #include "evconfig-private.h"
29 
30 #ifdef _WIN32
31 #include <winsock2.h>
32 #define WIN32_LEAN_AND_MEAN
33 #include <windows.h>
34 #undef WIN32_LEAN_AND_MEAN
35 #endif
36 
37 #include <sys/types.h>
38 #ifdef EVENT__HAVE_STDLIB_H
39 #include <stdlib.h>
40 #endif
41 #include <errno.h>
42 #include <limits.h>
43 #ifndef EVENT__HAVE_GETTIMEOFDAY
44 #include <sys/timeb.h>
45 #endif
46 #if !defined(EVENT__HAVE_NANOSLEEP) && !defined(EVENT__HAVE_USLEEP) && \
47 	!defined(_WIN32)
48 #include <sys/select.h>
49 #endif
50 #include <time.h>
51 #include <sys/stat.h>
52 #include <string.h>
53 
54 /** evutil_usleep_() */
55 #if defined(_WIN32)
56 #elif defined(EVENT__HAVE_NANOSLEEP)
57 #elif defined(EVENT__HAVE_USLEEP)
58 #include <unistd.h>
59 #endif
60 
61 #include "event2/util.h"
62 #include "util-internal.h"
63 #include "log-internal.h"
64 #include "mm-internal.h"
65 
66 #ifndef EVENT__HAVE_GETTIMEOFDAY
67 /* No gettimeofday; this must be windows. */
68 int
evutil_gettimeofday(struct timeval * tv,struct timezone * tz)69 evutil_gettimeofday(struct timeval *tv, struct timezone *tz)
70 {
71 #ifdef _MSC_VER
72 #define U64_LITERAL(n) n##ui64
73 #else
74 #define U64_LITERAL(n) n##llu
75 #endif
76 
77 	/* Conversion logic taken from Tor, which in turn took it
78 	 * from Perl.  GetSystemTimeAsFileTime returns its value as
79 	 * an unaligned (!) 64-bit value containing the number of
80 	 * 100-nanosecond intervals since 1 January 1601 UTC. */
81 #define EPOCH_BIAS U64_LITERAL(116444736000000000)
82 #define UNITS_PER_SEC U64_LITERAL(10000000)
83 #define USEC_PER_SEC U64_LITERAL(1000000)
84 #define UNITS_PER_USEC U64_LITERAL(10)
85 	union {
86 		FILETIME ft_ft;
87 		ev_uint64_t ft_64;
88 	} ft;
89 
90 	if (tv == NULL)
91 		return -1;
92 
93 	GetSystemTimeAsFileTime(&ft.ft_ft);
94 
95 	if (EVUTIL_UNLIKELY(ft.ft_64 < EPOCH_BIAS)) {
96 		/* Time before the unix epoch. */
97 		return -1;
98 	}
99 	ft.ft_64 -= EPOCH_BIAS;
100 	tv->tv_sec = (long) (ft.ft_64 / UNITS_PER_SEC);
101 	tv->tv_usec = (long) ((ft.ft_64 / UNITS_PER_USEC) % USEC_PER_SEC);
102 	return 0;
103 }
104 #endif
105 
106 #define MAX_SECONDS_IN_MSEC_LONG \
107 	(((LONG_MAX) - 999) / 1000)
108 
109 long
evutil_tv_to_msec_(const struct timeval * tv)110 evutil_tv_to_msec_(const struct timeval *tv)
111 {
112 	if (tv->tv_usec > 1000000 || tv->tv_sec > MAX_SECONDS_IN_MSEC_LONG)
113 		return -1;
114 
115 	return (tv->tv_sec * 1000) + ((tv->tv_usec + 999) / 1000);
116 }
117 
118 /*
119   Replacement for usleep on platforms that don't have one.  Not guaranteed to
120   be any more finegrained than 1 msec.
121  */
122 void
evutil_usleep_(const struct timeval * tv)123 evutil_usleep_(const struct timeval *tv)
124 {
125 	if (!tv)
126 		return;
127 #if defined(_WIN32)
128 	{
129 		long msec = evutil_tv_to_msec_(tv);
130 		Sleep((DWORD)msec);
131 	}
132 #elif defined(EVENT__HAVE_NANOSLEEP)
133 	{
134 		struct timespec ts;
135 		ts.tv_sec = tv->tv_sec;
136 		ts.tv_nsec = tv->tv_usec*1000;
137 		nanosleep(&ts, NULL);
138 	}
139 #elif defined(EVENT__HAVE_USLEEP)
140 	/* Some systems don't like to usleep more than 999999 usec */
141 	sleep(tv->tv_sec);
142 	usleep(tv->tv_usec);
143 #else
144 	{
145 		struct timeval tv2 = *tv;
146 		select(0, NULL, NULL, NULL, &tv2);
147 	}
148 #endif
149 }
150 
151 int
evutil_date_rfc1123(char * date,const size_t datelen,const struct tm * tm)152 evutil_date_rfc1123(char *date, const size_t datelen, const struct tm *tm)
153 {
154 	static const char *DAYS[] =
155 		{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
156 	static const char *MONTHS[] =
157 		{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
158 
159 	time_t t = time(NULL);
160 
161 #ifndef _WIN32
162 	struct tm sys;
163 #endif
164 
165 	/* If `tm` is null, set system's current time. */
166 	if (tm == NULL) {
167 #ifdef _WIN32
168 		/** TODO: detect _gmtime64()/_gmtime64_s() */
169 		tm = gmtime(&t);
170 #else
171 		gmtime_r(&t, &sys);
172 		tm = &sys;
173 #endif
174 	}
175 
176 	return evutil_snprintf(
177 		date, datelen, "%s, %02d %s %4d %02d:%02d:%02d GMT",
178 		DAYS[tm->tm_wday], tm->tm_mday, MONTHS[tm->tm_mon],
179 		1900 + tm->tm_year, tm->tm_hour, tm->tm_min, tm->tm_sec);
180 }
181 
182 /*
183    This function assumes it's called repeatedly with a
184    not-actually-so-monotonic time source whose outputs are in 'tv'. It
185    implements a trivial ratcheting mechanism so that the values never go
186    backwards.
187  */
188 static void
adjust_monotonic_time(struct evutil_monotonic_timer * base,struct timeval * tv)189 adjust_monotonic_time(struct evutil_monotonic_timer *base,
190     struct timeval *tv)
191 {
192 	evutil_timeradd(tv, &base->adjust_monotonic_clock, tv);
193 
194 	if (evutil_timercmp(tv, &base->last_time, <)) {
195 		/* Guess it wasn't monotonic after all. */
196 		struct timeval adjust;
197 		evutil_timersub(&base->last_time, tv, &adjust);
198 		evutil_timeradd(&adjust, &base->adjust_monotonic_clock,
199 		    &base->adjust_monotonic_clock);
200 		*tv = base->last_time;
201 	}
202 	base->last_time = *tv;
203 }
204 
205 /*
206    Allocate a new struct evutil_monotonic_timer
207  */
208 struct evutil_monotonic_timer *
evutil_monotonic_timer_new(void)209 evutil_monotonic_timer_new(void)
210 {
211   struct evutil_monotonic_timer *p = NULL;
212 
213   p = mm_malloc(sizeof(*p));
214   if (!p) goto done;
215 
216   memset(p, 0, sizeof(*p));
217 
218  done:
219   return p;
220 }
221 
222 /*
223    Free a struct evutil_monotonic_timer
224  */
225 void
evutil_monotonic_timer_free(struct evutil_monotonic_timer * timer)226 evutil_monotonic_timer_free(struct evutil_monotonic_timer *timer)
227 {
228   if (timer) {
229     mm_free(timer);
230   }
231 }
232 
233 /*
234    Set up a struct evutil_monotonic_timer for initial use
235  */
236 int
evutil_configure_monotonic_time(struct evutil_monotonic_timer * timer,int flags)237 evutil_configure_monotonic_time(struct evutil_monotonic_timer *timer,
238                                 int flags)
239 {
240   return evutil_configure_monotonic_time_(timer, flags);
241 }
242 
243 /*
244    Query the current monotonic time
245  */
246 int
evutil_gettime_monotonic(struct evutil_monotonic_timer * timer,struct timeval * tp)247 evutil_gettime_monotonic(struct evutil_monotonic_timer *timer,
248                          struct timeval *tp)
249 {
250   return evutil_gettime_monotonic_(timer, tp);
251 }
252 
253 
254 #if defined(HAVE_POSIX_MONOTONIC)
255 /* =====
256    The POSIX clock_gettime() interface provides a few ways to get at a
257    monotonic clock.  CLOCK_MONOTONIC is most widely supported.  Linux also
258    provides a CLOCK_MONOTONIC_COARSE with accuracy of about 1-4 msec.
259 
260    On all platforms I'm aware of, CLOCK_MONOTONIC really is monotonic.
261    Platforms don't agree about whether it should jump on a sleep/resume.
262  */
263 
264 int
evutil_configure_monotonic_time_(struct evutil_monotonic_timer * base,int flags)265 evutil_configure_monotonic_time_(struct evutil_monotonic_timer *base,
266     int flags)
267 {
268 	/* CLOCK_MONOTONIC exists on FreeBSD, Linux, and Solaris.  You need to
269 	 * check for it at runtime, because some older kernel versions won't
270 	 * have it working. */
271 #ifdef CLOCK_MONOTONIC_COARSE
272 	const int precise = flags & EV_MONOT_PRECISE;
273 #endif
274 	const int fallback = flags & EV_MONOT_FALLBACK;
275 	struct timespec	ts;
276 
277 #ifdef CLOCK_MONOTONIC_COARSE
278 	if (CLOCK_MONOTONIC_COARSE < 0) {
279 		/* Technically speaking, nothing keeps CLOCK_* from being
280 		 * negative (as far as I know). This check and the one below
281 		 * make sure that it's safe for us to use -1 as an "unset"
282 		 * value. */
283 		event_errx(1,"I didn't expect CLOCK_MONOTONIC_COARSE to be < 0");
284 	}
285 	if (! precise && ! fallback) {
286 		if (clock_gettime(CLOCK_MONOTONIC_COARSE, &ts) == 0) {
287 			base->monotonic_clock = CLOCK_MONOTONIC_COARSE;
288 			return 0;
289 		}
290 	}
291 #endif
292 	if (!fallback && clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
293 		base->monotonic_clock = CLOCK_MONOTONIC;
294 		return 0;
295 	}
296 
297 	if (CLOCK_MONOTONIC < 0) {
298 		event_errx(1,"I didn't expect CLOCK_MONOTONIC to be < 0");
299 	}
300 
301 	base->monotonic_clock = -1;
302 	return 0;
303 }
304 
305 int
evutil_gettime_monotonic_(struct evutil_monotonic_timer * base,struct timeval * tp)306 evutil_gettime_monotonic_(struct evutil_monotonic_timer *base,
307     struct timeval *tp)
308 {
309 	struct timespec ts;
310 
311 	if (base->monotonic_clock < 0) {
312 		if (evutil_gettimeofday(tp, NULL) < 0)
313 			return -1;
314 		adjust_monotonic_time(base, tp);
315 		return 0;
316 	}
317 
318 	if (clock_gettime(base->monotonic_clock, &ts) == -1)
319 		return -1;
320 	tp->tv_sec = ts.tv_sec;
321 	tp->tv_usec = ts.tv_nsec / 1000;
322 
323 	return 0;
324 }
325 #endif
326 
327 #if defined(HAVE_MACH_MONOTONIC)
328 /* ======
329    Apple is a little late to the POSIX party.  And why not?  Instead of
330    clock_gettime(), they provide mach_absolute_time().  Its units are not
331    fixed; we need to use mach_timebase_info() to get the right functions to
332    convert its units into nanoseconds.
333 
334    To all appearances, mach_absolute_time() seems to be honest-to-goodness
335    monotonic.  Whether it stops during sleep or not is unspecified in
336    principle, and dependent on CPU architecture in practice.
337  */
338 
339 int
evutil_configure_monotonic_time_(struct evutil_monotonic_timer * base,int flags)340 evutil_configure_monotonic_time_(struct evutil_monotonic_timer *base,
341     int flags)
342 {
343 	const int fallback = flags & EV_MONOT_FALLBACK;
344 	struct mach_timebase_info mi;
345 	memset(base, 0, sizeof(*base));
346 	/* OSX has mach_absolute_time() */
347 	if (!fallback &&
348 	    mach_timebase_info(&mi) == 0 &&
349 	    mach_absolute_time() != 0) {
350 		/* mach_timebase_info tells us how to convert
351 		 * mach_absolute_time() into nanoseconds, but we
352 		 * want to use microseconds instead. */
353 		mi.denom *= 1000;
354 		memcpy(&base->mach_timebase_units, &mi, sizeof(mi));
355 	} else {
356 		base->mach_timebase_units.numer = 0;
357 	}
358 	return 0;
359 }
360 
361 int
evutil_gettime_monotonic_(struct evutil_monotonic_timer * base,struct timeval * tp)362 evutil_gettime_monotonic_(struct evutil_monotonic_timer *base,
363     struct timeval *tp)
364 {
365 	ev_uint64_t abstime, usec;
366 	if (base->mach_timebase_units.numer == 0) {
367 		if (evutil_gettimeofday(tp, NULL) < 0)
368 			return -1;
369 		adjust_monotonic_time(base, tp);
370 		return 0;
371 	}
372 
373 	abstime = mach_absolute_time();
374 	usec = (abstime * base->mach_timebase_units.numer)
375 	    / (base->mach_timebase_units.denom);
376 	tp->tv_sec = usec / 1000000;
377 	tp->tv_usec = usec % 1000000;
378 
379 	return 0;
380 }
381 #endif
382 
383 #if defined(HAVE_WIN32_MONOTONIC)
384 /* =====
385    Turn we now to Windows.  Want monontonic time on Windows?
386 
387    Windows has QueryPerformanceCounter(), which gives time most high-
388    resolution time.  It's a pity it's not so monotonic in practice; it's
389    also got some fun bugs, especially: with older Windowses, under
390    virtualizations, with funny hardware, on multiprocessor systems, and so
391    on.  PEP418 [1] has a nice roundup of the issues here.
392 
393    There's GetTickCount64() on Vista and later, which gives a number of 1-msec
394    ticks since startup.  The accuracy here might be as bad as 10-20 msec, I
395    hear.  There's an undocumented function (NtSetTimerResolution) that
396    allegedly increases the accuracy. Good luck!
397 
398    There's also GetTickCount(), which is only 32 bits, but seems to be
399    supported on pre-Vista versions of Windows.  Apparently, you can coax
400    another 14 bits out of it, giving you 2231 years before rollover.
401 
402    The less said about timeGetTime() the better.
403 
404    "We don't care.  We don't have to.  We're the Phone Company."
405             -- Lily Tomlin, SNL
406 
407    Our strategy, if precise timers are turned off, is to just use the best
408    GetTickCount equivalent available.  If we've been asked for precise timing,
409    then we mostly[2] assume that GetTickCount is monotonic, and correct
410    GetPerformanceCounter to approximate it.
411 
412    [1] http://www.python.org/dev/peps/pep-0418
413    [2] Of course, we feed the Windows stuff into adjust_monotonic_time()
414        anyway, just in case it isn't.
415 
416  */
417 /*
418     Parts of our logic in the win32 timer code here are closely based on
419     BitTorrent's libUTP library.  That code is subject to the following
420     license:
421 
422       Copyright (c) 2010 BitTorrent, Inc.
423 
424       Permission is hereby granted, free of charge, to any person obtaining a
425       copy of this software and associated documentation files (the
426       "Software"), to deal in the Software without restriction, including
427       without limitation the rights to use, copy, modify, merge, publish,
428       distribute, sublicense, and/or sell copies of the Software, and to
429       permit persons to whom the Software is furnished to do so, subject to
430       the following conditions:
431 
432       The above copyright notice and this permission notice shall be included
433       in all copies or substantial portions of the Software.
434 
435       THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
436       OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
437       MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
438       NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
439       LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
440       OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
441       WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
442 */
443 
444 static ev_uint64_t
evutil_GetTickCount_(struct evutil_monotonic_timer * base)445 evutil_GetTickCount_(struct evutil_monotonic_timer *base)
446 {
447 	if (base->GetTickCount64_fn) {
448 		/* Let's just use GetTickCount64 if we can. */
449 		return base->GetTickCount64_fn();
450 	} else if (base->GetTickCount_fn) {
451 		/* Greg Hazel assures me that this works, that BitTorrent has
452 		 * done it for years, and this it won't turn around and
453 		 * bite us.  He says they found it on some game programmers'
454 		 * forum some time around 2007.
455 		 */
456 		ev_uint64_t v = base->GetTickCount_fn();
457 		return (DWORD)v | ((v >> 18) & 0xFFFFFFFF00000000);
458 	} else {
459 		/* Here's the fallback implementation. We have to use
460 		 * GetTickCount() with its given signature, so we only get
461 		 * 32 bits worth of milliseconds, which will roll ove every
462 		 * 49 days or so.  */
463 		DWORD ticks = GetTickCount();
464 		if (ticks < base->last_tick_count) {
465 			base->adjust_tick_count += ((ev_uint64_t)1) << 32;
466 		}
467 		base->last_tick_count = ticks;
468 		return ticks + base->adjust_tick_count;
469 	}
470 }
471 
472 int
evutil_configure_monotonic_time_(struct evutil_monotonic_timer * base,int flags)473 evutil_configure_monotonic_time_(struct evutil_monotonic_timer *base,
474     int flags)
475 {
476 	const int precise = flags & EV_MONOT_PRECISE;
477 	const int fallback = flags & EV_MONOT_FALLBACK;
478 	HANDLE h;
479 	memset(base, 0, sizeof(*base));
480 
481 	h = evutil_load_windows_system_library_(TEXT("kernel32.dll"));
482 	if (h != NULL && !fallback) {
483 		base->GetTickCount64_fn = (ev_GetTickCount_func)GetProcAddress(h, "GetTickCount64");
484 		base->GetTickCount_fn = (ev_GetTickCount_func)GetProcAddress(h, "GetTickCount");
485 	}
486 
487 	base->first_tick = base->last_tick_count = evutil_GetTickCount_(base);
488 	if (precise && !fallback) {
489 		LARGE_INTEGER freq;
490 		if (QueryPerformanceFrequency(&freq)) {
491 			LARGE_INTEGER counter;
492 			QueryPerformanceCounter(&counter);
493 			base->first_counter = counter.QuadPart;
494 			base->usec_per_count = 1.0e6 / freq.QuadPart;
495 			base->use_performance_counter = 1;
496 		}
497 	}
498 
499 	return 0;
500 }
501 
502 static inline ev_int64_t
abs64(ev_int64_t i)503 abs64(ev_int64_t i)
504 {
505 	return i < 0 ? -i : i;
506 }
507 
508 
509 int
evutil_gettime_monotonic_(struct evutil_monotonic_timer * base,struct timeval * tp)510 evutil_gettime_monotonic_(struct evutil_monotonic_timer *base,
511     struct timeval *tp)
512 {
513 	ev_uint64_t ticks = evutil_GetTickCount_(base);
514 	if (base->use_performance_counter) {
515 		/* Here's a trick we took from BitTorrent's libutp, at Greg
516 		 * Hazel's recommendation.  We use QueryPerformanceCounter for
517 		 * our high-resolution timer, but use GetTickCount*() to keep
518 		 * it sane, and adjust_monotonic_time() to keep it monotonic.
519 		 */
520 		LARGE_INTEGER counter;
521 		ev_int64_t counter_elapsed, counter_usec_elapsed, ticks_elapsed;
522 		QueryPerformanceCounter(&counter);
523 		counter_elapsed = (ev_int64_t)
524 		    (counter.QuadPart - base->first_counter);
525 		ticks_elapsed = ticks - base->first_tick;
526 		/* TODO: This may upset VC6. If you need this to work with
527 		 * VC6, please supply an appropriate patch. */
528 		counter_usec_elapsed = (ev_int64_t)
529 		    (counter_elapsed * base->usec_per_count);
530 
531 		if (abs64(ticks_elapsed*1000 - counter_usec_elapsed) > 1000000) {
532 			/* It appears that the QueryPerformanceCounter()
533 			 * result is more than 1 second away from
534 			 * GetTickCount() result. Let's adjust it to be as
535 			 * accurate as we can; adjust_monotnonic_time() below
536 			 * will keep it monotonic. */
537 			counter_usec_elapsed = ticks_elapsed * 1000;
538 			base->first_counter = (ev_uint64_t) (counter.QuadPart - counter_usec_elapsed / base->usec_per_count);
539 		}
540 		tp->tv_sec = (time_t) (counter_usec_elapsed / 1000000);
541 		tp->tv_usec = counter_usec_elapsed % 1000000;
542 
543 	} else {
544 		/* We're just using GetTickCount(). */
545 		tp->tv_sec = (time_t) (ticks / 1000);
546 		tp->tv_usec = (ticks % 1000) * 1000;
547 	}
548 	adjust_monotonic_time(base, tp);
549 
550 	return 0;
551 }
552 #endif
553 
554 #if defined(HAVE_FALLBACK_MONOTONIC)
555 /* =====
556    And if none of the other options work, let's just use gettimeofday(), and
557    ratchet it forward so that it acts like a monotonic timer, whether it
558    wants to or not.
559  */
560 
561 int
evutil_configure_monotonic_time_(struct evutil_monotonic_timer * base,int precise)562 evutil_configure_monotonic_time_(struct evutil_monotonic_timer *base,
563     int precise)
564 {
565 	memset(base, 0, sizeof(*base));
566 	return 0;
567 }
568 
569 int
evutil_gettime_monotonic_(struct evutil_monotonic_timer * base,struct timeval * tp)570 evutil_gettime_monotonic_(struct evutil_monotonic_timer *base,
571     struct timeval *tp)
572 {
573 	if (evutil_gettimeofday(tp, NULL) < 0)
574 		return -1;
575 	adjust_monotonic_time(base, tp);
576 	return 0;
577 
578 }
579 #endif
580