time.tcl

Go to the documentation of this file.
00001 /*  time.tcl - Copyright (C) 2003 Pat Thoyts <patthoyts@users.sourceforge.net>*/
00002 /* */
00003 /*  Client for the Time protocol. See RFC 868*/
00004 /*  Client for Simple Network Time Protocol - RFC 2030*/
00005 /* */
00006 /*  -------------------------------------------------------------------------*/
00007 /*  See the file "license.terms" for information on usage and redistribution*/
00008 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00009 /*  -------------------------------------------------------------------------*/
00010 /* */
00011 /*  $Id: time.tcl,v 1.19 2006/09/19 23:36:17 andreas_kupries Exp $*/
00012 
00013 package require Tcl 8.0;                /*  tcl minimum version*/
00014 package require log;                    /*  tcllib 1.3*/
00015 
00016 namespace ::time {
00017     variable version 1.2.1
00018     variable rcsid {$Id: time.tcl,v 1.19 2006/09/19 23:36:17 andreas_kupries Exp $}
00019 
00020     namespace export configure gettime server cleanup
00021 
00022     variable options
00023     if {![info exists options]} {
00024         array  options =  {
00025             -timeserver {}
00026             -port       37
00027             -protocol   tcp
00028             -timeout    10000
00029             -command    {}
00030             -loglevel   warning
00031         }
00032         if {![catch {package require udp}]} {
00033              options = (-protocol) udp
00034         } else {
00035             if {![catch {package require ceptcl}]} {
00036                  options = (-protocol) udp
00037             }
00038         }
00039         log::lvSuppressLE emergency 0
00040         log::lvSuppressLE $options(-loglevel) 1
00041         log::lvSuppress $options(-loglevel) 0
00042     }
00043 
00044     /*  Store conversions for other epochs. Currently only unix - but maybe*/
00045     /*  there are some others out there.*/
00046     variable epoch
00047     if {![info exists epoch]} {
00048         array  epoch =  {
00049             unix 2208988800
00050         }
00051     }
00052 
00053     /*  The id for the next token.*/
00054     variable uid
00055     if {![info exists uid]} {
00056          uid =  0
00057     }
00058 }
00059 
00060 /*  -------------------------------------------------------------------------*/
00061 
00062 /*  Description:*/
00063 /*   Retrieve configuration settings for the time package.*/
00064 /* */
00065 ret  ::time::cget (type optionname) {
00066     return [configure $optionname]
00067 }
00068 
00069 /*  Description:*/
00070 /*   Configure the package.*/
00071 /*   With no options, returns a list of all current settings.*/
00072 /* */
00073 ret  ::time::configure (type args) {
00074     variable options
00075     set r {}
00076     set cget 0
00077 
00078     if {[llength $args] < 1} {
00079         foreach opt [lsort [array names options]] {
00080             lappend r $opt $options($opt)
00081         }
00082         return $r
00083     }
00084 
00085     if {[llength $args] == 1} {
00086         set cget 1
00087     }
00088 
00089     while {[string match -* [set option [lindex $args 0]]]} {
00090         switch -glob -- $option {
00091             -port     { set r [SetOrGet -port $cget] }
00092             -timeout  { set r [SetOrGet -timeout $cget] }
00093             -protocol { set r [SetOrGet -protocol $cget] }
00094             -command  { set r [SetOrGet -command $cget] }
00095             -loglevel {
00096                 if {$cget} {
00097                     return $options(-loglevel)
00098                 } else {
00099                     set options(-loglevel) [Pop args 1]
00100                     log::lvSuppressLE emergency 0
00101                     log::lvSuppressLE $options(-loglevel) 1
00102                     log::lvSuppress $options(-loglevel) 0
00103                 }
00104             }
00105             --        { Pop args ; break }
00106             default {
00107                 set err [join [lsort [array names options -*]] ", "]
00108                 return -code error "bad option \"$option\": must be $err"
00109             }
00110         }
00111         Pop args
00112     }
00113     
00114     return $r
00115 }
00116 
00117 /*  Set/get package options.*/
00118 ret  ::time::SetOrGet (type option , optional cget =0) {
00119     upvar options options
00120     upvar args args
00121     if {$cget} {
00122         return $options($option)
00123     } else {
00124         set options($option) [Pop args 1]
00125     }
00126     return {}
00127 }
00128 
00129 /*  -------------------------------------------------------------------------*/
00130 
00131 ret  ::time::getsntp (type args) {
00132     set token [eval [linsert $args 0 CommonSetup -port 123]]
00133     upvar #0 $token State
00134     set State(rfc) 2030
00135     return [QueryTime $token]
00136 }
00137 
00138 ret  ::time::gettime (type args) {
00139     set token [eval [linsert $args 0 CommonSetup -port 37]]
00140     upvar #0 $token State
00141     set State(rfc) 868
00142     return [QueryTime $token]
00143 }
00144 
00145 ret  ::time::CommonSetup (type args) {
00146     variable options
00147     variable uid
00148     set token [namespace current]::[incr uid]
00149     variable $token
00150     upvar 0 $token State
00151 
00152     array set State [array get options]
00153     set State(status) unconnected
00154     set State(data) {}
00155     
00156     while {[string match -* [set option [lindex $args 0]]]} {
00157         switch -glob -- $option {
00158             -port     { set State(-port) [Pop args 1] }
00159             -timeout  { set State(-timeout) [Pop args 1] }
00160             -proto*   { set State(-protocol) [Pop args 1] }
00161             -command  { set State(-command) [Pop args 1] }
00162             --        { Pop args ; break }
00163             default {
00164                 set err [join [lsort [array names State -*]] ", "]
00165                 return -code error "bad option \"$option\":\
00166                     must be $err."
00167             }
00168         }
00169         Pop args
00170     }
00171 
00172     set len [llength $args]
00173     if {$len < 1 || $len > 2} {
00174         if {[catch {info level -1} arg0]} {
00175             set arg0 [info level 0]
00176         }
00177         return -code error "wrong # args: should be\
00178               \"[lindex $arg0 0] ?options? timeserver ?port?\""
00179     }
00180 
00181     set State(-timeserver) [lindex $args 0]
00182     if {$len == 2} {
00183         set State(-port) [lindex $args 1]
00184     }
00185 
00186     return $token
00187 }
00188 
00189 ret  ::time::QueryTime (type token) {
00190     variable $token
00191     upvar 0 $token State
00192 
00193     if {[string equal $State(-protocol) "udp"]} {
00194         if {[llength [package provide ceptcl]] == 0 \
00195                 && [llength [package provide udp]] == 0} {
00196             set State(status) error
00197             set State(error) "udp support is not available, \
00198                 either ceptcl or tcludp required"
00199             return $token
00200         }
00201     }
00202 
00203     if {[catch {
00204         if {[string equal $State(-protocol) "udp"]} {
00205             if {[llength [package provide ceptcl]] > 0} {
00206                 # using ceptcl
00207                 set State(sock) [cep -type datagram \
00208                                      $State(-timeserver) $State(-port)]
00209                 fconfigure $State(sock) -blocking 0
00210             } else {
00211                 # using tcludp
00212                 set State(sock) [udp_open]
00213                 udp_conf $State(sock) $State(-timeserver) $State(-port)
00214             }
00215         } else {
00216             set State(sock) [socket $State(-timeserver) $State(-port)]
00217         }
00218     } sockerr]} {
00219         set State(status) error
00220         set State(error) $sockerr
00221         return $token
00222     }
00223 
00224     # setup the timeout
00225     if {$State(-timeout) > 0} {
00226         set State(after) [after $State(-timeout) \
00227                               [list [namespace origin reset] $token timeout]]
00228     }
00229 
00230     set State(status) connect
00231     fconfigure $State(sock) -translation binary -buffering none
00232 
00233     # SNTP wants a 48 byte request while TIME doesn't care and is happy
00234     # to accept any old rubbish. If protocol is TCP then merely connecting
00235     # is sufficient to elicit a response.
00236     if {[string equal $State(-protocol) "udp"]} {
00237         set len [expr {($State(rfc) == 2030) ? 47 : 3}]
00238         puts -nonewline $State(sock) \x0b[string repeat \0 $len]
00239     }
00240 
00241     fileevent $State(sock) readable \
00242         [list [namespace origin ClientReadEvent] $token]
00243 
00244     if {$State(-command) == {}} {
00245         wait $token
00246     }
00247 
00248     return $token
00249 }
00250 
00251 ret  ::time::unixtime (optional token ={)} {
00252     variable $token
00253     variable epoch
00254     upvar 0 $token State
00255     if {$State(status) != "ok"} {
00256         return -code error $State(error)
00257     }
00258     
00259     /*  SNTP returns 48+ bytes while TIME always returns 4.*/
00260     if {[string length $State(data)] == 4} {
00261         /*  RFC848 TIME*/
00262         if {[binary scan $State(data) I r] < 1} {
00263             return -code error "Unable to scan data"
00264         }
00265         return [expr {int($r - $epoch(unix))&0xffffffff}]
00266     } elseif {[string length $State(data)] > 47} {
00267         /*  SNTP TIME*/
00268         if {[binary scan $State(data) c40II -> sec frac] < 1} {
00269             return -code error "Failed to decode result"
00270         }
00271         return [expr {int($sec - $epoch(unix))&0xffffffff}]
00272     } else {
00273         return -code error "error: data format not recognised"
00274     }
00275 }
00276 
00277 ret  ::time::status (type token) {
00278     variable $token
00279     upvar 0 $token State
00280     return $State(status)
00281 }
00282 
00283 ret  ::time::error (type token) {
00284     variable $token
00285     upvar 0 $token State
00286     set r {}
00287     if {[info exists State(error)]} {
00288         set r $State(error)
00289     }
00290     return $r
00291 }
00292 
00293 ret  ::time::wait (type token) {
00294     variable $token
00295     upvar 0 $token State
00296 
00297     if {$State(status) == "connect"} {
00298         vwait [subst $token](status)
00299     }
00300 
00301     return $State(status)
00302 }
00303 
00304 ret  ::time::reset (type token , optional why =reset) {
00305     variable $token
00306     upvar 0 $token State
00307     set reason {}
00308     set State(status) $why
00309     catch {fileevent $State(sock) readable {}}
00310     if {$why == "timeout"} {
00311         set reason "timeout ocurred"
00312     }
00313     Finish $token $reason
00314 }
00315 
00316 /*  Description:*/
00317 /*   Remove any state associated with this token.*/
00318 /* */
00319 ret  ::time::cleanup (type token) {
00320     variable $token
00321     upvar 0 $token State
00322     if {[info exists State]} {
00323         unset State
00324     }
00325 }
00326 
00327 /*  -------------------------------------------------------------------------*/
00328 
00329 ret  ::time::ClientReadEvent (type token) {
00330     variable $token
00331     upvar 0 $token State
00332 
00333     append State(data) [read $State(sock)]
00334     set expected [expr {($State(rfc) == 868) ? 4 : 48}]
00335     if {[string length $State(data)] < $expected} { return }
00336 
00337     #FIX ME: acquire peer data?
00338 
00339     set State(status) ok
00340     Finish $token
00341     return
00342 }
00343 
00344 ret  ::time::Finish (type token , optional errormsg ={)} {
00345     variable $token
00346     upvar 0 $token State
00347     global errorInfo errorCode
00348 
00349     if {[string length $errormsg] > 0} {
00350      State = (error) $errormsg
00351      State = (status) error
00352     }
00353     catch {close $State(sock)}
00354     catch {after cancel $State(after)}
00355     if {[info exists State(-command)] && $State(-command) != {}} {
00356         if {[catch {eval $State(-command) {$token}} err]} {
00357             if {[string length $errormsg] == 0} {
00358                  State = (error) [list $err $errorInfo $errorCode]
00359                  State = (status) error
00360             }
00361         }
00362         if {[info exists State(-command)]} {
00363             un State = (-command)
00364         }
00365     }
00366 }
00367 
00368 /*  -------------------------------------------------------------------------*/
00369 /*  Description:*/
00370 /*   Pop the nth element off a list. Used in options processing.*/
00371 /* */
00372 ret  ::time::Pop (type varname , optional nth =0) {
00373     upvar $varname args
00374     set r [lindex $args $nth]
00375     set args [lreplace $args $nth $nth]
00376     return $r
00377 }
00378 
00379 /*  -------------------------------------------------------------------------*/
00380 
00381 package provide time $::time::version
00382 
00383 /*  -------------------------------------------------------------------------*/
00384 /*  Local variables:*/
00385 /*    mode: tcl*/
00386 /*    indent-tabs-mode: nil*/
00387 /*  End:*/
00388 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1