1 /*
2  *      $Id: time.c 1090 2012-03-27 19:40:42Z aaron $
3  */
4 /************************************************************************
5  *                                                                      *
6  *                             Copyright (C)  2002                      *
7  *                                Internet2                             *
8  *                             All Rights Reserved                      *
9  *                                                                      *
10  ************************************************************************/
11 /*
12  *        File:         time.c
13  *
14  *        Author:       Jeff W. Boote
15  *                      Internet2
16  *
17  *        Date:         Thu May 30 11:37:48 MDT 2002
18  *
19  *        Description:
20  *
21  *        functions to encode and decode OWPTimeStamp into 8 octet
22  *        buffer for transmitting over the network.
23  *
24  *        The format for a timestamp messages is as follows:
25  *
26  *           0                   1                   2                   3
27  *           0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
28  *          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
29  *        00|                Integer part of seconds                        |
30  *          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
31  *        04|              Fractional part of seconds                       |
32  *          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
33  *
34  *        The format for an Error Estimate is:
35  *           0                   1
36  *           0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
37  *          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
38  *        00|S|Z|   Scale   |   Multiplier  |
39  *          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
40  *
41  */
42 #include <owamp/owampP.h>
43 
44 #include <string.h>
45 #include <assert.h>
46 #include <math.h>
47 #include <sys/time.h>
48 #ifdef  HAVE_SYS_TIMEX_H
49 #include <sys/timex.h>
50 #endif
51 
52 static struct timeval  timeoffset;
53 static int sign_timeoffset = 0;
54 
55 /*
56  * Function:        _OWPInitNTP
57  *
58  * Description:
59  *         Initialize NTP.
60  *
61  * In Args:
62  *
63  * Out Args:
64  *
65  * Scope:
66  * Returns:
67  * Side Effect:
68  *
69  * If STA_NANO is defined, we insist it is set, this way we can be sure that
70  * ntp_gettime is returning a timespec and not a timeval.
71  *
72  * TODO: The correct way to fix this is:
73  * 1. If ntptimeval contains a struct timespec - then use nano's period.
74  * 2. else if STA_NANO is set, then use nano's.
75  * 3. else ???(mills solution requires root - ugh)
76  *    will this work?
77  *    (do a timing test:
78  *                 gettimeofday(A);
79  *                 getntptime(B);
80  *                 nanosleep(1000);
81  *                 getntptime(C);
82  *                 gettimeofday(D);
83  *
84  *                 1. Interprete B and C as usecs
85  *                         if(D-A < C-B)
86  *                                 nano's
87  *                         else
88  *                                 usecs
89  */
90 int
_OWPInitNTP(OWPContext ctx)91 _OWPInitNTP(
92         OWPContext  ctx
93         )
94 {
95     char    *toffstr=NULL;
96 
97     /*
98      * If the system has NTP system calls use them. Otherwise
99      * timestamps will be marked UNSYNC.
100      */
101 #ifdef  HAVE_SYS_TIMEX_H
102     {
103         struct timex        ntp_conf;
104 
105 	memset(&ntp_conf,0,sizeof(ntp_conf));
106         if(ntp_adjtime(&ntp_conf) < 0){
107             OWPError(ctx,OWPErrFATAL,OWPErrUNKNOWN,"ntp_adjtime(): %M");
108             return 1;
109         }
110 
111         if(ntp_conf.status & STA_UNSYNC){
112             OWPError(ctx,OWPErrFATAL,OWPErrUNKNOWN,
113                     "NTP: Status UNSYNC (clock offset issues likely)");
114         }
115 
116 #ifdef        STA_NANO
117         if( !(ntp_conf.status & STA_NANO)){
118             OWPError(ctx,OWPErrFATAL,OWPErrUNKNOWN,
119                     "NTP: STA_NANO should be set. Make sure ntpd is running, and your NTP configuration is good.");
120         }
121 #endif
122     }
123 #else
124     OWPError(ctx,OWPErrFATAL,OWPErrUNKNOWN,
125             "NTP syscalls unavail: Status UNSYNC (clock offset issues likely)");
126 #endif  /* HAVE_SYS_TIMEX_H */
127 
128     if( !(toffstr = getenv("OWAMP_DEBUG_TIMEOFFSET"))){
129         timeoffset.tv_sec = 0;
130         timeoffset.tv_usec = 0;
131     }
132     else{
133         double  td;
134         char    *estr=NULL;
135 
136         td = strtod(toffstr,&estr);
137         if((toffstr == estr) || (errno == ERANGE)){
138             OWPError(ctx,OWPErrFATAL,OWPErrUNKNOWN,
139                     "Invalid OWAMP_DEBUG_TIMEOFFSET env var: %s",toffstr);
140             return 1;
141         }
142 
143         if(td == 0.0){
144             sign_timeoffset = 0;
145         }
146         else{
147             if(td > 0.0){
148                 sign_timeoffset = 1;
149             }
150             else{
151                 sign_timeoffset = -1;
152                 td = -td;
153             }
154 
155             timeoffset.tv_sec = trunc(td);
156             td -= timeoffset.tv_sec;
157             td *= 1000000;
158             timeoffset.tv_usec = trunc(td);
159 
160             OWPError(ctx,OWPErrDEBUG,OWPErrUNKNOWN,
161                     "OWAMP_DEBUG_TIMEOFFSET: sec=%c%lu, usec=%lu",
162                     (sign_timeoffset > 0)?'+':'-',
163                     timeoffset.tv_sec,timeoffset.tv_usec);
164         }
165     }
166 
167     return 0;
168 }
169 
170 struct timespec *
_OWPGetTimespec(OWPContext ctx,struct timespec * ts,uint32_t * esterr,uint8_t * sync)171 _OWPGetTimespec(
172         OWPContext      ctx         __attribute__((unused)),
173         struct timespec *ts,
174         uint32_t       *esterr,
175         uint8_t        *sync
176         )
177 {
178     struct timeval  tod;
179     uint32_t        timeerr;
180 
181     /*
182      * By default, assume the clock is unsynchronized.
183      */
184     *sync = 0;
185     timeerr = (uint32_t)0;
186 
187     if(gettimeofday(&tod,NULL) != 0){
188         OWPError(ctx,OWPErrFATAL,OWPErrUNKNOWN,"gettimeofday(): %M");
189         return NULL;
190     }
191 
192     if(sign_timeoffset > 0){
193         tvaladd(&tod,&timeoffset);
194     }
195     else if(sign_timeoffset < 0){
196         tvalsub(&tod,&timeoffset);
197     }
198 
199     /* assign localtime */
200     ts->tv_sec = tod.tv_sec;
201     ts->tv_nsec = tod.tv_usec * 1000;        /* convert to nsecs */
202 
203     /*
204      * If ntp system calls are available use them to determine
205      * time error.
206      */
207 #ifdef HAVE_SYS_TIMEX_H
208     {
209         struct timex        ntp_conf;
210 
211         memset(&ntp_conf,0,sizeof(ntp_conf));
212         if(ntp_adjtime(&ntp_conf) < 0){
213             OWPError(ctx,OWPErrFATAL,OWPErrUNKNOWN,"ntp_adjtime(): %M");
214             return NULL;
215         }
216 
217         /*
218          * Check sync flag
219          */
220         if(!(ntp_conf.status & STA_UNSYNC)){
221             long    sec;
222 
223             *sync = 1;
224             /*
225              * Apply ntp "offset"
226              */
227 #ifdef        STA_NANO
228             if(ntp_conf.status & STA_NANO)
229                 sec = 1000000000;
230             else
231                 sec = 1000000;
232 #else
233             sec = 1000000;
234 #endif
235             /*
236              * Convert negative offsets to positive ones by decreasing
237              * the ts->tv_sec.
238              */
239             while(ntp_conf.offset < 0){
240                 ts->tv_sec--;
241                 ntp_conf.offset += sec;
242             }
243 
244             /*
245              * Make sure the "offset" is less than 1 second
246              */
247             while(ntp_conf.offset >= sec){
248                 ts->tv_sec++;
249                 ntp_conf.offset -= sec;
250             }
251 
252 #ifdef        STA_NANO
253             if(!(ntp_conf.status & STA_NANO))
254                 ntp_conf.offset *= 1000;
255 #else
256             ntp_conf.offset *= 1000;
257 #endif
258             ts->tv_nsec += ntp_conf.offset;
259             if(ts->tv_nsec >= 1000000000){
260                 ts->tv_sec++;
261                 ts->tv_nsec -= 1000000000;
262             }
263 
264             timeerr = (uint32_t)ntp_conf.esterror;
265         }
266 
267     }
268 #endif
269 
270     /*
271      * Set estimated error
272      */
273     *esterr = timeerr;
274 
275     /*
276      * Make sure a non-zero error is always returned - perfection
277      * is not allowed if SYNC is true. ;)
278      */
279     if(*sync && !*esterr){
280         *esterr = 1;
281     }
282 
283     return ts;
284 }
285 
286 /*
287  * Function:        OWPGetTimeOfDay
288  *
289  * Description:
290  *         mimic's unix gettimeofday but takes OWPTimestamp's instead
291  *         of struct timeval's.
292  *
293  * In Args:
294  *
295  * Out Args:
296  *
297  * Scope:
298  * Returns:
299  * Side Effect:
300  */
301 OWPTimeStamp *
OWPGetTimeOfDay(OWPContext ctx,OWPTimeStamp * tstamp)302 OWPGetTimeOfDay(
303         OWPContext      ctx,
304         OWPTimeStamp    *tstamp
305         )
306 {
307     struct timespec ts;
308     uint32_t       esterr;
309 
310     if(!tstamp)
311         return NULL;
312 
313     if(!(_OWPGetTimespec(ctx,&ts,&esterr,&tstamp->sync)))
314         return NULL;
315 
316     return OWPTimespecToTimestamp(tstamp,&ts,&esterr,NULL);
317 }
318 
319 /*
320  * Function:        _OWPEncodeTimeStamp
321  *
322  * Description:
323  *                 Takes an OWPTimeStamp structure and encodes the time
324  *                 value from that structure to the byte array in network
325  *                 byte order appropriate for sending the value over the wire.
326  *                 (See above format diagram.)
327  *
328  * In Args:
329  *
330  * Out Args:
331  *
332  * Scope:
333  * Returns:
334  * Side Effect:
335  */
336 void
_OWPEncodeTimeStamp(uint8_t buf[8],OWPTimeStamp * tstamp)337 _OWPEncodeTimeStamp(
338         uint8_t         buf[8],
339         OWPTimeStamp    *tstamp
340         )
341 {
342     uint32_t   t32;
343 
344     assert(tstamp);
345     assert(buf);
346 
347     /*
348      * seconds - Most Significant 32 bits hold the seconds in
349      * host byte order. Set t32 to this value in network byte order,
350      * then copy them to bytes 0-4 in buf.
351      */
352     t32 = htonl((tstamp->owptime >> 32) & 0xFFFFFFFF);
353     memcpy(&buf[0],&t32,4);
354 
355     /*
356      * frac seconds - Least significant 32 bits hold the fractional
357      * seconds in host byte order. Set t32 to this value in network
358      * byte order, then copy them to bytes 5-8 in buf.
359      */
360     t32 = htonl(tstamp->owptime & 0xFFFFFFFF);
361     memcpy(&buf[4],&t32,4);
362 
363     return;
364 }
365 
366 /*
367  * Function:        _OWPEncodeTimeStampErrEstimate
368  *
369  * Description:
370  *                 Takes an OWPTimeStamp structure and encodes the time
371  *                 error estimate value from that structure to the byte array
372  *                 in network order as appropriate for sending the value over
373  *                 the wire. (See above format diagram.)
374  *
375  * In Args:
376  *
377  * Out Args:
378  *
379  * Scope:
380  * Returns:
381  * Side Effect:
382  */
383 OWPBoolean
_OWPEncodeTimeStampErrEstimate(uint8_t buf[2],OWPTimeStamp * tstamp)384 _OWPEncodeTimeStampErrEstimate(
385         uint8_t         buf[2],
386         OWPTimeStamp    *tstamp
387         )
388 {
389     assert(tstamp);
390     assert(buf);
391 
392     /*
393      * If multiplier is 0, this is an invalid error estimate.
394      */
395     if(!tstamp->multiplier){
396         return False;
397     }
398 
399     /*
400      * Scale is 6 bit quantity, and first 2 bits MUST be zero here.
401      */
402     buf[0] = tstamp->scale & 0x3F;
403 
404     /*
405      * Set the first bit for sync.
406      */
407     if(tstamp->sync){
408         buf[0] |= 0x80;
409     }
410 
411     buf[1] = tstamp->multiplier;
412 
413     return True;
414 }
415 
416 /*
417  * Function:        _OWPDecodeTimeStamp
418  *
419  * Description:
420  *                 Takes a buffer of 8 bytes of owamp protocol timestamp
421  *                 information and saves it in the OWPTimeStamp structure
422  *                 in the owptime OWPNum64 field. (See above format diagram
423  *                 for owamp protocol timestamp format, and owamp.h header
424  *                 file for a description of the OWPNum64 type.)
425  *
426  * In Args:
427  *
428  * Out Args:
429  *
430  * Scope:
431  * Returns:
432  * Side Effect:
433  */
434 void
_OWPDecodeTimeStamp(OWPTimeStamp * tstamp,uint8_t buf[8])435 _OWPDecodeTimeStamp(
436         OWPTimeStamp    *tstamp,
437         uint8_t         buf[8]
438         )
439 {
440     uint32_t   t32;
441 
442     assert(tstamp);
443     assert(buf);
444 
445     /*
446      * First clear owptime.
447      */
448     memset(&tstamp->owptime,0,8);
449 
450     /*
451      * seconds is first 4 bytes in network byte order.
452      * copy to a 32 bit int, correct the byte order, then assign
453      * to the most significant 32 bits of owptime.
454      */
455     memcpy(&t32,&buf[0],4);
456     tstamp->owptime = (OWPNum64)(ntohl(t32)) << 32;
457 
458     /*
459      * fractional seconds are the next 4 bytes in network byte order.
460      * copy to a 32 bit int, correct the byte order, then assign to
461      * the least significant 32 bits of owptime.
462      */
463     memcpy(&t32,&buf[4],4);
464     tstamp->owptime |= (ntohl(t32) & 0xFFFFFFFF);
465 
466     return;
467 }
468 
469 /*
470  * Function:        _OWPDecodeTimeStampErrEstimate
471  *
472  * Description:
473  *                 Takes a buffer of 2 bytes of owamp protocol timestamp
474  *                 error estimate information and saves it in the OWPTimeStamp
475  *                 structure. (See above format diagram for owamp protocol
476  *                 timestamp error estimate format, and owamp.h header
477  *                 file for a description of the OWPNum64 type.)
478  *
479  * In Args:
480  *
481  * Out Args:
482  *
483  * Scope:
484  * Returns:
485  *                 True if the ErrEstimate is valid, False if it is not.
486  * Side Effect:
487  */
488 OWPBoolean
_OWPDecodeTimeStampErrEstimate(OWPTimeStamp * tstamp,uint8_t buf[2])489 _OWPDecodeTimeStampErrEstimate(
490         OWPTimeStamp    *tstamp,
491         uint8_t         buf[2]
492         )
493 {
494     assert(tstamp);
495     assert(buf);
496 
497     /*
498      * If multiplier is 0, this is an invalid timestamp. From here, just
499      * set sync and scale to 0 as well.
500      */
501     if(!buf[1]){
502         buf[0] = 0;
503     }
504 
505     tstamp->sync = (buf[0] & 0x80)?1:0;
506     tstamp->scale = buf[0] & 0x3F;
507     tstamp->multiplier = buf[1];
508 
509     return (tstamp->multiplier != 0);
510 }
511 
512 /*
513  * Function:        OWPTimevalToTimestamp
514  *
515  * Description:
516  *         This function takes a struct timeval and converts the time value
517  *         to an OWPTimeStamp. This function assumes the struct timeval is
518  *         an absolute time offset from unix epoch (0h Jan 1, 1970), and
519  *         converts the time to an OWPTimeStamp which uses time similar to
520  *         the description in RFC 1305 (NTP). i.e. epoch is 0h Jan 1, 1900.
521  *
522  *         The Error Estimate of the OWPTimeStamp structure is invalidated
523  *         in this function. (A struct timeval gives no indication of the
524  *         error.)
525  *
526  * In Args:
527  *
528  * Out Args:
529  *
530  * Scope:
531  * Returns:
532  * Side Effect:
533  */
534 OWPTimeStamp *
OWPTimevalToTimestamp(OWPTimeStamp * tstamp,struct timeval * tval)535 OWPTimevalToTimestamp(
536         OWPTimeStamp    *tstamp,
537         struct timeval  *tval
538         )
539 {
540     /*
541      * Ensure valid tstamp, tval - and ensure scale of tv_nsec is valid
542      */
543     if(!tstamp || !tval)
544         return NULL;
545 
546     /*
547      * Now convert representation.
548      */
549     OWPTimevalToNum64(&tstamp->owptime,tval);
550 
551     /*
552      * Convert "epoch"'s - must do after conversion or there is the risk
553      * of overflow since time_t is a 32bit signed quantity instead of
554      * unsigned.
555      */
556     tstamp->owptime = OWPNum64Add(tstamp->owptime,
557             OWPULongToNum64(OWPJAN_1970));
558 
559     return tstamp;
560 }
561 
562 /*
563  * Function:        OWPTimestampToTimeval
564  *
565  * Description:
566  *         This function takes an OWPTimeStamp structure and returns a
567  *         valid struct timeval based on the time value encoded in it.
568  *         This function assumees the OWPTimeStamp is holding an absolute
569  *         time value, and is not holding a relative time. i.e. It assumes
570  *         the time value is relative to NTP epoch.
571  *
572  *         The Error Estimate of the OWPTimeStamp structure is ignored by
573  *         this function. (A struct timeval gives no indication of the error.)
574  *
575  * In Args:
576  *
577  * Out Args:
578  *
579  * Scope:
580  * Returns:
581  * Side Effect:
582  */
583 struct timeval *
OWPTimestampToTimeval(struct timeval * tval,OWPTimeStamp * tstamp)584 OWPTimestampToTimeval(
585         struct timeval  *tval,
586         OWPTimeStamp    *tstamp
587         )
588 {
589     OWPNum64    tnum;
590 
591     if(!tval || !tstamp)
592         return NULL;
593 
594     /*
595      * Convert "epoch"'s - must do before conversion or there is the risk
596      * of overflow since time_t is a 32bit signed quantity instead of
597      * unsigned.
598      */
599     tnum = OWPNum64Sub(tstamp->owptime, OWPULongToNum64(OWPJAN_1970));
600     OWPNum64ToTimeval(tval,tnum);
601 
602     return tval;
603 }
604 
605 /*
606  * Function:        OWPTimespecToTimestamp
607  *
608  * Description:
609  *         This function takes a struct timespec and converts it to an
610  *         OWPTimeStamp. The timespec is assumed to be an absolute time
611  *         relative to unix epoch. The OWPTimeStamp will be an absolute
612  *         time relative to 0h Jan 1, 1900.
613  *
614  *         If errest is not set, then parts of the OWPTimeStamp that deal
615  *         with the error estimate. (scale, multiplier, sync) will be
616  *         set to 0.
617  *
618  *         If errest is set, sync will be unmodified. (An errest of 0 is
619  *         NOT valid, and will be treated as if errest was not set.)
620  *
621  *         Scale and Multiplier will be set from the value of errest.
622  *
623  *         If last_errest is set, then Scale and Multiplier will be left
624  *         unmodified if (*errest == *last_errest).
625  *
626  * In Args:
627  *
628  * Out Args:
629  *
630  * Scope:
631  * Returns:
632  * Side Effect:
633  */
634 OWPTimeStamp *
OWPTimespecToTimestamp(OWPTimeStamp * tstamp,struct timespec * tval,uint32_t * errest,uint32_t * last_errest)635 OWPTimespecToTimestamp(
636         OWPTimeStamp    *tstamp,
637         struct timespec *tval,
638         uint32_t       *errest,        /* usec's */
639         uint32_t       *last_errest
640         )
641 {
642     /*
643      * Ensure valid tstamp, tval - and ensure scale of tv_nsec is valid
644      */
645     if(!tstamp || !tval)
646         return NULL;
647 
648     /*
649      * Now convert representation.
650      */
651     OWPTimespecToNum64(&tstamp->owptime,tval);
652 
653     /*
654      * Convert "epoch"'s - must do after conversion or there is the risk
655      * of overflow since time_t is a 32bit signed quantity instead of
656      * unsigned.
657      */
658     tstamp->owptime = OWPNum64Add(tstamp->owptime,
659             OWPULongToNum64(OWPJAN_1970));
660 
661     /*
662      * If errest is set, and is non-zero.
663      */
664     if(errest && *errest){
665         /*
666          * If last_errest is set, and the error hasn't changed,
667          * then we don't touch the prec portion assuming it is
668          * already correct.
669          */
670         if(!last_errest || (*errest != *last_errest)){
671             OWPNum64        err;
672 
673             /*
674              * First normalize errest to 32bit fractional seconds.
675              */
676             err = OWPUsecToNum64(*errest);
677 
678             /*
679              * Just in the unlikely event that err is represented
680              * by a type larger than 64 bits...
681              * (This ensures that scale will not overflow the
682              * 6 bits available to it.)
683              */
684             err &= (uint64_t)0xFFFFFFFFFFFFFFFFULL;
685 
686             /*
687              * Now shift err until it will fit in an 8 bit
688              * multiplier (after adding one for rounding err: this
689              * is the reason a value of 0xFF is shifted one last
690              * time), counting the shifts to set the scale.
691              */
692             tstamp->scale = 0;
693             while(err >= 0xFF){
694                 err >>= 1;
695                 tstamp->scale++;
696             }
697             err++;        /* rounding error:represents shifted off bits */
698             tstamp->multiplier = 0xFF & err;
699         }
700     }
701     else{
702         tstamp->sync = 0;
703         tstamp->scale = 64;
704         tstamp->multiplier = 1;
705     }
706 
707     return tstamp;
708 }
709 
710 /*
711  * Function:        OWPTimestampToTimespec
712  *
713  * Description:
714  *         This function takes an OWPTimeStamp structure and returns a
715  *         valid struct timespec based on the time value encoded in it.
716  *         This function assumees the OWPTimeStamp is holding an absolute
717  *         time value, and is not holding a relative time. i.e. It assumes
718  *         the time value is relative to NTP epoch.
719  *
720  *         The Error Estimate of the OWPTimeStamp structure is ignored by
721  *         this function. (A struct timespec gives no indication of the error.)
722  *
723  * In Args:
724  *
725  * Out Args:
726  *
727  * Scope:
728  * Returns:
729  * Side Effect:
730  */
731 struct timespec *
OWPTimestampToTimespec(struct timespec * tval,OWPTimeStamp * tstamp)732 OWPTimestampToTimespec(
733         struct timespec *tval,
734         OWPTimeStamp    *tstamp
735         )
736 {
737     OWPNum64    tnum;
738 
739     if(!tval || !tstamp)
740         return NULL;
741 
742     /*
743      * Convert "epoch"'s - must do before conversion or there is the risk
744      * of overflow since time_t is a 32bit signed quantity instead of
745      * unsigned.
746      */
747     tnum = OWPNum64Sub(tstamp->owptime, OWPULongToNum64(OWPJAN_1970));
748     OWPNum64ToTimespec(tval,tnum);
749 
750     return tval;
751 }
752 
753 /*
754  * Function:        OWPGetTimeStampError
755  *
756  * Description:
757  *         Retrieve the timestamp error estimate as a double in seconds.
758  *
759  * In Args:
760  *
761  * Out Args:
762  *
763  * Scope:
764  * Returns:
765  * Side Effect:
766  */
767 double
OWPGetTimeStampError(OWPTimeStamp * tstamp)768 OWPGetTimeStampError(
769         OWPTimeStamp    *tstamp
770         )
771 {
772     OWPNum64    err;
773     uint8_t    scale;
774 
775     if(!tstamp)
776         return 0.0;
777 
778     /*
779      * Place multiplier in 64bit int large enough to hold full value.
780      * (Due to the interpretation of OWPNum64 being 32 bits of seconds,
781      * and 32 bits of "fraction", this effectively divides by 2^32.)
782      */
783     err = tstamp->multiplier & 0xFF;
784 
785     /*
786      * Now shift it based on the "scale".
787      * (This affects the 2^scale multiplication.)
788      */
789     scale = tstamp->scale & 0x3F;
790     while(scale>0){
791         err <<= 1;
792         scale--;
793     }
794 
795     /*
796      * Return the OWPNum64 value as a double.
797      */
798     return OWPNum64ToDouble(err);
799 }
800