skalibs

Mirror/fork of https://skarnet.org/software/skalibs/
git clone https://ccx.te2000.cz/git/skalibs
Log | Files | Refs | README | LICENSE

tai.html (20525B)


      1 <html>
      2   <head>
      3     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      4     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
      5     <meta http-equiv="Content-Language" content="en" />
      6     <title>skalibs: the tai library interface</title>
      7     <meta name="Description" content="skalibs: the tai library interface" />
      8     <meta name="Keywords" content="skalibs c unix tai library libstddjb" />
      9     <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
     10   </head>
     11 <body>
     12 
     13 <p>
     14 <a href="index.html">libstddjb</a><br />
     15 <a href="../libskarnet.html">libskarnet</a><br />
     16 <a href="../index.html">skalibs</a><br />
     17 <a href="//skarnet.org/software/">Software</a><br />
     18 <a href="//skarnet.org/">skarnet.org</a>
     19 </p>
     20 
     21 <h1> The <tt>tai</tt> library interface </h1>
     22 
     23 <p>
     24  The following functions are declared in the <tt>skalibs/tai.h</tt> header,
     25 and implemented in the <tt>libskarnet.a</tt> or <tt>libskarnet.so</tt> library.
     26 </p>
     27 
     28 <h2> General information </h2>
     29 
     30 <p>
     31  <tt>tai</tt> is a set of data structures and primitives to represent
     32 and perform operations on time.
     33 </p>
     34 
     35 <p>
     36  The point of <tt>tai</tt> is to handle time without ever having to
     37 deal with annoyances such as Y2K, Y2038, NTP limits, non-linear
     38 clocks, and the like. By using the <tt>tai</tt> interface, you ensure
     39 your program will behave properly no matter what.
     40 </p>
     41 
     42 <h3> What is the problem&nbsp;? </h3>
     43 
     44 <p>
     45  The standard APIs for time management under Unix are broken in more
     46 or less subtle ways. The most obvious thing is that they do not pass
     47 year 2038. A less obvious problem is that they do not handle leap
     48 seconds correctly. Here are a few references you should read to
     49 understand what is going on:
     50 </p>
     51 
     52 <ul>
     53  <li> <a href="http://www.madore.org/~david/misc/time.html">David Madore's page
     54 on time</a>. It's outdated (there was a leap second in 2009), but complete. </li>
     55  <li> From David Madore again, more to the point: a
     56 <a href="http://www.madore.org/~david/computers/unix-leap-seconds.html">page
     57 about leap seconds, UTC and TAI</a>. </li>
     58  <li> The skalibs <a href="../flags.html#clockistai">--enable-tai-clock</a>
     59 documentation. </li>
     60  <li> <a href="https://cr.yp.to/proto/utctai.html">Dan J. Bernstein's page
     61 on UTC, TAI and Unix time</a>. </li>
     62 </ul>
     63 
     64 <p>
     65  The meat and potatoes of all this is that programmers cannot simply rely on
     66 standard Unix APIs such as
     67 <a href="https://pubs.opengroup.org/onlinepubs/9699919799/functions/gettimeofday.html">gettimeofday()</a>
     68 (which, by the way, is marked as obsolescent, but it's not going to disappear tomorrow)
     69 to measure time intervals or even to give precise absolute time, and in
     70 any case those APIs will become obsolete in 2038.
     71 </p>
     72 
     73 <h3> So what does <tt>tai</tt> do&nbsp;? </h3>
     74 
     75 <p>
     76  <tt>tai</tt> implements - among other things - the
     77 <a href="https://cr.yp.to/libtai/tai64.html">TAI64 and TAI64N</a>
     78 formats, which are used in all of skalibs. This gives a programmer access
     79 to precise <em>linear absolute time</em>, which is suitable for both
     80 timestamping (<em>wallclock</em> usage) and time interval measurements
     81 (<em>stopwatch</em> usage). Additionally, TAI64 passes Y2038 (it can
     82 represent dates exceeding the estimated lifespan of the universe).
     83 </p>
     84 
     85 <p>
     86  <tt>tai</tt> has been inspired by Dan J. Bernstein's
     87 <a href="https://cr.yp.to/libtai.html">libtai</a> library, but does not
     88 borrow any code from it.
     89 </p>
     90 
     91 <h2> Data structures </h2>
     92 
     93 <p>
     94  A <tt>tai</tt> structure holds an absolute date with a one-second
     95 precision. A <tt>tain</tt> structure holds an absolute date with a
     96 maximum of one-nanosecond precision, as permitted by the underlying system
     97 call. If <a href="../flags.html#usert">flag-usert</a> is clear, the system
     98 clock will be read via
     99 <a href="https://www.opengroup.org/onlinepubs/9699919799/functions/gettimeofday.html">gettimeofday()</a>
    100 system call, which has a one-microsecond precision; if it is set, the
    101 system clock will be read via the 
    102 <a href="https://www.opengroup.org/onlinepubs/9699919799/functions/clock_gettime.html">clock_gettime()</a>
    103 system call, which has a one-nanosecond precision. In either case, a current
    104 <tt>tain</tt> will be unable to be more precise than the underlying
    105 implementation.
    106 </p>
    107 
    108 <p>
    109  A <tt>tai</tt>, as well as a <tt>tain</tt>, can also
    110 hold a (possibly negative) relative time, i.e. a difference of absolute
    111 dates. It is up to the programmer to make sure that a relative time is
    112 never interpreted as an absolute TAI64 date, and vice-versa.
    113 </p>
    114 
    115 <h2> Functions </h2>
    116 
    117 <h3> Wallclock operations </h3>
    118 
    119 <p>
    120 <code> int tai_now (tai *t) </code> <br />
    121 Writes the current time as a TAI value to *<em>t</em>, with a
    122 1-second precision. The current time is based on the system clock.
    123 Make sure skalibs has been compiled with or without the
    124 <a href="../flags.html#clockistai">--enable-tai-clock</a> configure option according
    125 to your system clock synchronization method: skalibs supports a
    126 system clock set to TAI-10 or to UTC.
    127 The function returns 1 if it succeeds, or 0 (and sets errno) if
    128 it fails.
    129 </p>
    130 
    131 <p>
    132 <code> int sysclock_get (tain *a) </code> <br />
    133 Reads the current value of the system clock (as in CLOCK_REALTIME) into *<em>a</em>.
    134 Returns 1 if it succeeds or 0 (and sets errno) if it fails.
    135 Note that despite being a <tt>tain</tt>, *<em>a</em>
    136 <strong>does not contain a TAI value</strong> - it only contains
    137 an internal, Y2038-safe representation of the value of the system
    138 clock, which should be either TAI-10 or UTC. You should not use
    139 this function directly unless you know exactly what you are doing.
    140 </p>
    141 
    142 <p>
    143 <code> int sysclock_set (tain const *a) </code> <br />
    144 Sets the system clock to *<em>a</em>, provided *<em>a</em> has
    145 the correct internal representation. You should not use this
    146 function directly unless you know exactly what you are doing.
    147 </p>
    148 
    149 <p>
    150 <code> int tain_wallclock_read (tain *a) </code> <br />
    151 Reads the current time into *<em>a</em>, as a TAI64N value.
    152 Returns 1 if it succeeds or 0 (and sets errno) if it fails.
    153 Here <em>a</em> contains a valid TAI64N stamp, no matter what the
    154 system clock is set to: arithmetic operations can be performed
    155 on it.
    156 </p>
    157 
    158 <p>
    159 <code> int tain_setnow (tain const *a) </code> <br />
    160 Sets the current time to *<em>a</em>.
    161 Returns 1 if it succeeds or 0 (and sets errno) if it fails.
    162 <em>a</em> must contain a valid TAI64N stamp; proper
    163 operations will be automatically run to convert that stamp into
    164 the right format for the system clock.
    165 </p>
    166 
    167 <p>
    168 <code> int tain_now_set_wallclock (tain *a) </code> <br />
    169 Tells skalibs that future invocations of <tt>tain_now()</tt>
    170 (see below) should use a wall clock, i.e. the system time
    171 as returned by <tt>clock_gettime(CLOCK_REALTIME)</tt> or
    172 <tt>gettimeofday()</tt>. Also reads the current time into *<em>a</em>.
    173 Returns 1 if it succeeds or 0 (and sets errno) if it fails.
    174 A wall clock is the default: it is not necessary
    175 to call this function before invoking <tt>tain_now()</tt> at the
    176 start of a program.
    177 </p>
    178 
    179 <h3> Stopwatch operations </h3>
    180 
    181 <p>
    182 The following two operations can only succeed if your system provides the
    183 <a href="https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_gettime.html">clock_gettime()</a>
    184 primitive with at least one of the CLOCK_MONOTONIC or CLOCK_BOOTTIME clocks.
    185 Otherwise, they will fail with errno set to ENOSYS.
    186 </p>
    187 
    188 <p>
    189 <code> int tain_stopwatch_init (tain *a, clock_t cl, tain *offset) </code> <br />
    190 Initializes a stopwatch in *<em>offset</em>, using a clock named <em>cl</em>.
    191 Typically, <em>cl</em> is something like CLOCK_MONOTONIC, when it is defined
    192 by the system. The actual value of
    193 *<em>offset</em> is meaningless to the user; <em>offset</em>'s only
    194 use is to be given as a second parameter to <tt>tain_stopwatch_read()</tt>.
    195 The function returns 1 if it succeeds or 0 (and sets errno) if it fails.
    196 On success, the current time, as given by the <em>system clock</em> (a
    197 wall clock), is returned in *<em>a</em>.
    198 </p>
    199 
    200 <p>
    201  What <tt>tain_stopwatch_init()</tt> does is synchronize the "stopwatch
    202 clock" to the system clock. Right after <tt>tain_stopwatch_init()</tt>
    203 has been called, the absolute times given
    204 by <tt>tain_stopwatch_read()</tt> and <tt>tain_wallclock_read()</tt> are
    205 the same. Then, depending on the accuracy of the system clock, a drift
    206 may appear; calling <tt>tain_stopwatch_init()</tt> again resets that drift
    207 to zero.
    208 </p>
    209 
    210 <p>
    211 <code> int tain_stopwatch_read (tain *a, clock_t cl, tain const *offset) </code> <br />
    212  Gives the absolute time, as a TAI64N value, in *<em>a</em>. This
    213 absolute time is computed as a linear increase (as measured with
    214 the <em>cl</em> clock, which should be a monotonic clock such as
    215 CLOCK_MONOTONIC) since the last time <tt>tain_stopwatch_init()</tt>
    216 was called with parameter <em>offset</em>. <tt>tain_stopwatch_read()</tt>
    217 guarantees precise time interval measurements; however, the time it
    218 gives can slightly differ from the result of <tt>tain_wallclock_read()</tt>.
    219 The function returns 1 if it succeeds or 0 (and sets errno) if it fails.
    220 </p>
    221 
    222 <p>
    223 <code> int tain_now_set_stopwatch (tain *a) </code> <br />
    224 Tells skalibs that future invocations of <tt>tain_now()</tt>
    225 (see below) should use a stopwatch, i.e. the system time
    226 as returned by <tt>clock_gettime(CLOCK_MONOTONIC)</tt> or similar,
    227 if supported by the system. This is useful when it is more important
    228 for a program to compute unchanging time intervals no matter what the
    229 system clock does, than to display absolute time that is in sync with a
    230 human view of time (which is the cause and reason of most system clock
    231 jumps). <br />
    232 If no monotonic clock is supported by the system, this function does
    233 not change what <tt>tain_now()</tt> refers to (i.e. it will keep
    234 referring to the system clock). <br />
    235 Returns 1 on success and 0 (and sets errno) on failure. On success,
    236 the current time, as given by the <em>system clock</em> (a wall clock),
    237 is returned in *<em>a</em>.
    238 </p>
    239 
    240 <h3> All-purpose time reading </h3>
    241 
    242 <p>
    243 <code> int tain_now (tain *a) </code> <br />
    244 Writes the current time, as a TAI value, to *<em>a</em>. This is the
    245 function you should use by default. It returns 1 if it succeeds or
    246 0 (and sets errno) if it fails.
    247 </p>
    248 
    249 <p>
    250  <tt>tain_now()</tt> relies on the concept that there is One True Time Source
    251 for the process, and that is where it reads time from. By default, the
    252 One True Time Source is the system clock (a wall clock), and <tt>tain_now()</tt>
    253 is actually an alias to <tt>tain_wallclock_read()</tt>. At the start of a
    254 program, calling <tt>tain_now_set_stopwatch()</tt> will define a monotonic
    255 clock (if supported by the system) as the One True Time Source, which will
    256 make <tt>tain_now()</tt> resistant to system clock jumps, but will also
    257 make it unsuitable for timestamping.
    258 </p>
    259 
    260 <p>
    261  In other words: the <em>first</em> time you need to read the clock, or
    262 at the start of your program, you should use
    263 <tt>tain_now_set_wallclock()</tt> or <tt>tain_now_set_stopwatch()</tt>
    264 depending on whether you want the One True Time Source to be the system
    265 clock (CLOCK_REALTIME or <tt>gettimeofday()</tt>) or a stopwatch
    266 (CLOCK_MONOTONIC, if supported). Afterwards, every time you need to read
    267 from that time source, use <tt>tain_now()</tt>. skalibs functions that
    268 may block, such as <tt>iopause_g</tt>, internally call <tt>tain_now()</tt>
    269 to update the timestamp they're using, so they will use the time source
    270 you have defined. (Functions ending in <tt>_g</tt>
    271 use the STAMP global variable to store the current timestamp.)
    272 </p>
    273 
    274 
    275 <h3> Converting to/from libc representations </h3>
    276 
    277 <p>
    278  The following functions only convert from a certain structure
    279 format to another; they do not make any assumption about the
    280 format of the time contained in those structures. For instance,
    281 for the <tt>tai_from_timeval</tt> function, if the struct timeval
    282 contains an absolute UTC time, then the tai will also contain
    283 the same UTC time. Despite being a tai, it may contain
    284 something else than TAI time.
    285 </p>
    286 
    287 <p>
    288 If you need conversion from the native machine
    289 system clock format to TAI, see the next section.
    290 </p>
    291 
    292 <p>
    293 <code> int tai_from_time (tai *t, time_t u) <br />
    294 int tai_relative_from_time (tai *t, time_t u) </code> <br />
    295 Those functions convert an absolute (resp. relative) time in a
    296 time_t to an absolute (resp. relative)
    297 time in a tai, with a 1-second precision. They return 1,
    298 unless the time_t value is invalid (in which case they return 0).
    299 </p>
    300 
    301 <p>
    302 <code> int time_from_tai (time_t *u, tai const *t) <br />
    303 int time_from_tai_relative (time_t *u, tai const *t) </code> <br />
    304 The inverse functions of the previous ones. Be aware that
    305 time_t is 32 bits on some systems and cannot store all values
    306 of a tai (in which case the functions will return 0 EOVERFLOW).
    307 </p>
    308 
    309 <p>
    310 <code> int tain_from_timeval (tain *a, struct timeval const *tv) <br />
    311 int tain_relative_from_timeval (tain *a, struct timeval const *tv) <br />
    312 int tain_from_timespec (tain *a, struct timespec const *ts) <br />
    313 int tain_relative_from_timespec (tain *a, struct timespec const *ts) <br />
    314 int timeval_from_tain (struct timeval *tv, tain const *a) <br />
    315 int timeval_from_tain_relative (struct timeval *tv, tain const *a) <br />
    316 int timespec_from_tain (struct timespec *ts, tain const *a) <br />
    317 int timespec_from_tain_relative (struct timespec *ts, tain const *a) </code> <br />
    318 Same conversion operations, between <tt>tain</tt> and a
    319 <tt>struct timeval</tt> or <tt>struct timespec</tt>. The 1-microsecond
    320 (for <tt>struct timeval</tt>) or 1-nanosecond (for <tt>struct timespec</tt>)
    321 precision is preserved.
    322 </p>
    323 
    324 <h3> Conversion between TAI and the system clock format </h3>
    325 
    326 <p>
    327  Unlike the previous functions, the functions listed here will
    328 always operate on valid absolute timestamps. Only TAI64 time is
    329 stored in tai structures, and only TAI64N time is stored in
    330 tain structures. These functions will convert to/from TAI,
    331 from/to the machine system clock format, i.e. TAI-10 or UTC
    332 depending on whether skalibs was
    333 compiled with the --enable-tai-clock configure option).
    334 This is useful to get valid TAI/TAI64N timestamps out of
    335 information exported by the system, for instance the time_t
    336 returned by <a href="https://pubs.opengroup.org/onlinepubs/9699919799/functions/time.html">time()</a>,
    337 or in the <tt>st_atim</tt>, <tt>st_mtim</tt> or
    338 <tt>st_ctim</tt> fields of a <tt>struct stat</tt>.
    339 </p>
    340 
    341 <p>
    342  The functions return 1 in case of success, or 0 if the conversion
    343 could not be performed; in which case errno is set to EINVAL if
    344 the input argument was not a valid timestamp, to EOVERFLOW if the
    345 output could not be represented in the chosen format (which may
    346 happen on systems with a 32 bit <tt>time_t</tt>), or other error
    347 codes.
    348 </p>
    349 
    350 <code> int tai_from_time_sysclock (tai *a, time_t t) <br />
    351 int time_sysclock_from_tai (time_t *t, tai const *a) <br />
    352 int tain_from_timeval_sysclock (tain *a, struct timeval const *tv) <br />
    353 int timeval_sysclock_from_tain (struct timeval *tv, tain const *a) <br />
    354 int tain_from_timespec_sysclock (tain *a, struct timespec const *ts) <br />
    355 int timespec_sysclock_from_tain (struct timespec *ts, tain const *a) </code>
    356 
    357 <h3> Conversions to/from basic types </h3>
    358 
    359 <p>
    360 <code> int tain_uint (tain *a, unsigned int c) </code> <br />
    361 Stores a relative time of <em>c</em> seconds into <em>a</em>.
    362 Normally returns 1, but may return 0 EINVAL on pathological numbers.
    363 </p>
    364 
    365 <p>
    366 <code> int tain_from_millisecs (tain *a, int ms) </code> <br />
    367 This function makes a <tt>tain</tt> representing a relative
    368 time of <em>ms</em> milliseconds. <em>ms</em> must be non-negative.
    369 The function returns 1, unless <em>ms</em> is negative, in which case
    370 it returns 0 EINVAL.
    371 </p>
    372 
    373 <p>
    374 <code> int taino_millisecs (tain const *a) </code> <br />
    375 If *<em>a</em> contains a non-negative relative time that fits into
    376 a 31-bit integer number of milliseconds, the function returns that
    377 number. Else it returns -1 EINVAL.
    378 </p>
    379 
    380 <h3> Time computations </h3>
    381 
    382 <p>
    383 <code> int tai_add (tai *t, tai const *t1, tai const *t2) </code> <br />
    384 Stores *<em>t1</em> + *<em>t2</em> into <em>t</em>. Of course, *<em>t1</em>
    385 and *<em>t2</em> must not both represent absolute times.
    386 The function normally returns 1, but will return 0 on bad inputs.
    387 </p>
    388 
    389 <p>
    390 <code> int tai_sub (tai *t, tai const *t1, tai const *t2) </code> <br />
    391 Stores *<em>t1</em> - *<em>t2</em> into <em>t</em>. *<em>t1</em> cannot
    392 be relative if *<em>t2</em> is absolute. If they are both relative or
    393 both absolute, then *<em>t</em> is relative, else it's absolute.
    394 The function normally returns 1, but will return 0 on bad inputs.
    395 </p>
    396 
    397 <p>
    398 <code> int tain_add (tain *a, tain const *a1, tain const *a2) <br />
    399 int tain_sub (tain *a, tain const *a1, tain const *a2) </code> <br />
    400 Same thing with <tt>tain</tt>.
    401 </p>
    402 
    403 <p>
    404 <code> int tain_addsec (tain *a, tain const *a1, int c) </code> <br />
    405 Adds <em>c</em> seconds to *<em>a1</em> and stores the result into <em>a</em>.
    406 <em>c</em> may be negative.
    407 </p>
    408 
    409 <p>
    410 <code> void tain_half (tain *a, tain const *b) </code> <br />
    411 Stores *<em>b</em>/2 into <em>a</em>. *<em>b</em> must be relative.
    412 </p>
    413 
    414 <h3> Comparing dates or durations </h3>
    415 
    416 <p>
    417 <code> int tai_less (tai const *t1, tai const *t2) <br />
    418 int tain_less (tain const *t1, tain const *t2) </code> <br />
    419 Those functions return nonzero iff *<em>t1</em> is lesser than *<em>t2</em>.
    420 *<em>t1</em> and *<em>t2</em> must be both relative, or both absolute.
    421 </p>
    422 
    423 <h3> Packing and unpacking </h3>
    424 
    425 <p>
    426 <code> void tai_pack (char *s, tai const *t) </code> <br />
    427 Marshals *<em>t</em> into the buffer <em>s</em> points to, which
    428 must be preallocated with at least TAI_PACK (8) characters. Afterwards,
    429 the buffer contains the
    430 <a href="https://cr.yp.to/libtai/tai64.html#tai64">external TAI64 format</a>
    431 representation of *<em>t</em>.
    432 </p>
    433 
    434 <p>
    435 <code> void tai_unpack (char const *s, tai *t) </code> <br />
    436 Unmarshals the
    437 <a href="https://cr.yp.to/libtai/tai64.html#tai64">external TAI64 format</a>
    438 label pointed to by <em>s</em> (at least TAI_PACK characters) and stores
    439 the result into <em>t</em>.
    440 </p>
    441 
    442 <p>
    443 <code> void tain_pack (char *s, tain const *a) <br />
    444 void tain_unpack (char const *s, tain *a) </code> <br />
    445 Same thing with
    446 <a href="https://cr.yp.to/libtai/tai64.html#tai64n">external TAI64N format</a>,
    447 using TAIN_PACK (12) characters.
    448 </p>
    449 
    450 <h3> Formatting and scanning </h3>
    451 
    452 <p>
    453 <code> unsigned int tain_fmt (char *s, tain const *a) </code> <br />
    454 Writes into <em>s</em> an ASCII representation of *<em>a</em> in external
    455 TAI64N format. <em>s</em> must point to a preallocated buffer of at least
    456 TAIN_PACK*2 (24) characters. The function returns the number of bytes that
    457 have been written to <em>s</em> (24).
    458 </p>
    459 
    460 <p>
    461 <code> unsigned int tain_scan (char const *s, tain *a) </code> <br />
    462 Reads 24 characters from <em>s</em>; if those characters are a valid ASCII
    463 representation of the external TAI64N format of some time value, this value
    464 is stored into <em>a</em>, and 24 is returned. Else 0 is returned.
    465 </p>
    466 
    467 <a name="timestamp"><h3> Timestamping </h3></a>
    468 
    469 <p>
    470  A <em>TAI64N timestamp</em> is a string of 25 characters: a single '@'
    471 character followed by the ASCII representation of the TAI64N external
    472 format of an absolute date.
    473 </p>
    474 
    475 <p>
    476 <code> unsigned int timestamp_fmt (char *s, tain const *a) </code> <br />
    477 Writes a TAI64N timestamp representing the absolute date *<em>a</em>
    478 into the 25 characters pointed to by <em>s</em>. Returns 25.
    479 </p>
    480 
    481 <p>
    482 <code> unsigned int timestamp_scan (char const *s, tain *a) </code> <br />
    483 Reads 25 characters at <em>s</em>. If those characters are a valid TAI64N
    484 timestamp, stores the absolute date in <em>a</em> and returns 25. Else,
    485 returns 0.
    486 </p>
    487 
    488 <p>
    489 <code> int timestamp (char *s) </code> <br />
    490 Writes the current time (read from the system clock) as a TAI64N timestamp
    491 into <em>s</em>. Returns 1 if it succeeds or 0 (and sets errno) if it fails.
    492 </p>
    493 
    494 <p>
    495  TAI64N timestamps are an efficient, robust, and easy-to-use way of
    496 timestampping log lines. They're easy to recognize in automatic data
    497 parsers. Log files where every line starts with a TAI64N timestamp can
    498 be merged and alphanumerically sorted: the resulting file will be
    499 chronologically sorted.
    500 </p>
    501 
    502 <p>
    503  The <a href="//skarnet.org/software/s6/">s6</a> package
    504 provides tools to convert TAI64N timestamps into human-readable
    505 dates. Please do not embed human-readable dates in your log files,
    506 thus making parsing tools unnecessarily hard to write;
    507 use TAI64N timestamps instead, design tools that can parse them,
    508 and translate them to human-readable form at human analysis time.
    509 </p>
    510 
    511 </body>
    512 </html>