File indexing completed on 2024-05-12 06:03:09

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category  Zend
0016  * @package   Zend_TimeSync
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license   http://framework.zend.com/license/new-bsd     New BSD License
0019  * @version   $Id$
0020  */
0021 
0022 /**
0023  * Zend_TimeSync_Protocol
0024  */
0025 // require_once 'Zend/TimeSync/Protocol.php';
0026 
0027 /**
0028  * NTP Protocol handling class
0029  *
0030  * @category  Zend
0031  * @package   Zend_TimeSync
0032  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0033  * @license   http://framework.zend.com/license/new-bsd     New BSD License
0034  */
0035 class Zend_TimeSync_Ntp extends Zend_TimeSync_Protocol
0036 {
0037     /**
0038      * NTP port number (123) assigned by the Internet Assigned Numbers Authority
0039      *
0040      * @var integer
0041      */
0042     protected $_port = 123;
0043 
0044     /**
0045      * NTP class constructor, sets the timeserver and port number
0046      *
0047      * @param string  $timeserver Adress of the timeserver to connect to
0048      * @param integer $port       (Optional) Port for this timeserver
0049      */
0050     public function __construct($timeserver, $port = 123)
0051     {
0052         $this->_timeserver = 'udp://' . $timeserver;
0053         if ($port !== null) {
0054             $this->_port = $port;
0055         }
0056     }
0057 
0058     /**
0059      * Prepare local timestamp for transmission in our request packet
0060      *
0061      * NTP timestamps are represented as a 64-bit fixed-point number, in
0062      * seconds relative to 0000 UT on 1 January 1900.  The integer part is
0063      * in the first 32 bits and the fraction part in the last 32 bits
0064      *
0065      * @return string
0066      */
0067     protected function _prepare()
0068     {
0069         $frac   = microtime();
0070         $fracba = ($frac & 0xff000000) >> 24;
0071         $fracbb = ($frac & 0x00ff0000) >> 16;
0072         $fracbc = ($frac & 0x0000ff00) >> 8;
0073         $fracbd = ($frac & 0x000000ff);
0074 
0075         $sec   = (time() + 2208988800);
0076         $secba = ($sec & 0xff000000) >> 24;
0077         $secbb = ($sec & 0x00ff0000) >> 16;
0078         $secbc = ($sec & 0x0000ff00) >> 8;
0079         $secbd = ($sec & 0x000000ff);
0080 
0081         // Flags
0082         $nul       = chr(0x00);
0083         $nulbyte   = $nul . $nul . $nul . $nul;
0084         $ntppacket = chr(0xd9) . $nul . chr(0x0a) . chr(0xfa);
0085 
0086         /*
0087          * Root delay
0088          *
0089          * Indicates the total roundtrip delay to the primary reference
0090          * source at the root of the synchronization subnet, in seconds
0091          */
0092         $ntppacket .= $nul . $nul . chr(0x1c) . chr(0x9b);
0093 
0094         /*
0095          * Clock Dispersion
0096          *
0097          * Indicates the maximum error relative to the primary reference source at the
0098          * root of the synchronization subnet, in seconds
0099          */
0100         $ntppacket .= $nul . chr(0x08) . chr(0xd7) . chr(0xff);
0101 
0102         /*
0103          * ReferenceClockID
0104          *
0105          * Identifying the particular reference clock
0106          */
0107         $ntppacket .= $nulbyte;
0108 
0109         /*
0110          * The local time, in timestamp format, at the peer when its latest NTP message
0111          * was sent. Contanis an integer and a fractional part
0112          */
0113         $ntppacket .= chr($secba)  . chr($secbb)  . chr($secbc)  . chr($secbd);
0114         $ntppacket .= chr($fracba) . chr($fracbb) . chr($fracbc) . chr($fracbd);
0115 
0116         /*
0117          * The local time, in timestamp format, at the peer. Contains an integer
0118          * and a fractional part.
0119          */
0120         $ntppacket .= $nulbyte;
0121         $ntppacket .= $nulbyte;
0122 
0123         /*
0124          * This is the local time, in timestamp format, when the latest NTP message from
0125          * the peer arrived. Contanis an integer and a fractional part.
0126          */
0127         $ntppacket .= $nulbyte;
0128         $ntppacket .= $nulbyte;
0129 
0130         /*
0131          * The local time, in timestamp format, at which the
0132          * NTP message departed the sender. Contanis an integer
0133          * and a fractional part.
0134          */
0135         $ntppacket .= chr($secba)  . chr($secbb)  . chr($secbc)  . chr($secbd);
0136         $ntppacket .= chr($fracba) . chr($fracbb) . chr($fracbc) . chr($fracbd);
0137 
0138         return $ntppacket;
0139     }
0140 
0141     /**
0142      * Calculates a 32bit integer
0143      *
0144      * @param string $input
0145      * @return integer
0146      */
0147     protected function _getInteger($input)
0148     {
0149         $f1  = str_pad(ord($input[0]), 2, '0', STR_PAD_LEFT);
0150         $f1 .= str_pad(ord($input[1]), 2, '0', STR_PAD_LEFT);
0151         $f1 .= str_pad(ord($input[2]), 2, '0', STR_PAD_LEFT);
0152         $f1 .= str_pad(ord($input[3]), 2, '0', STR_PAD_LEFT);
0153         return (int) $f1;
0154     }
0155 
0156     /**
0157      * Calculates a 32bit signed fixed point number
0158      *
0159      * @param string $input
0160      * @return float
0161      */
0162     protected function _getFloat($input)
0163     {
0164         $f1  = str_pad(ord($input[0]), 2, '0', STR_PAD_LEFT);
0165         $f1 .= str_pad(ord($input[1]), 2, '0', STR_PAD_LEFT);
0166         $f1 .= str_pad(ord($input[2]), 2, '0', STR_PAD_LEFT);
0167         $f1 .= str_pad(ord($input[3]), 2, '0', STR_PAD_LEFT);
0168         $f2  = $f1 >> 17;
0169         $f3  = ($f1 & 0x0001FFFF);
0170         $f1  = $f2 . '.' . $f3;
0171         return (float) $f1;
0172     }
0173 
0174     /**
0175      * Calculates a 64bit timestamp
0176      *
0177      * @param string $input
0178      * @return float
0179      */
0180     protected function _getTimestamp($input)
0181     {
0182         $f1  = (ord($input[0]) * pow(256, 3));
0183         $f1 += (ord($input[1]) * pow(256, 2));
0184         $f1 += (ord($input[2]) * pow(256, 1));
0185         $f1 += (ord($input[3]));
0186         $f1 -= 2208988800;
0187 
0188         $f2  = (ord($input[4]) * pow(256, 3));
0189         $f2 += (ord($input[5]) * pow(256, 2));
0190         $f2 += (ord($input[6]) * pow(256, 1));
0191         $f2 += (ord($input[7]));
0192 
0193         return (float) ($f1 . "." . $f2);
0194     }
0195 
0196     /**
0197      * Reads the data returned from the timeserver
0198      *
0199      * This will return an array with binary data listing:
0200      *
0201      * @return array
0202      * @throws Zend_TimeSync_Exception When timeserver can not be connected
0203      */
0204     protected function _read()
0205     {
0206         $flags = ord(fread($this->_socket, 1));
0207         $info  = stream_get_meta_data($this->_socket);
0208 
0209         if ($info['timed_out'] === true) {
0210             fclose($this->_socket);
0211             throw new Zend_TimeSync_Exception('could not connect to ' .
0212                 "'$this->_timeserver' on port '$this->_port', reason: 'server timed out'");
0213         }
0214 
0215         $result = array(
0216             'flags'          => $flags,
0217             'stratum'        => ord(fread($this->_socket, 1)),
0218             'poll'           => ord(fread($this->_socket, 1)),
0219             'precision'      => ord(fread($this->_socket, 1)),
0220             'rootdelay'      => $this->_getFloat(fread($this->_socket, 4)),
0221             'rootdispersion' => $this->_getFloat(fread($this->_socket, 4)),
0222             'referenceid'    => fread($this->_socket, 4),
0223             'referencestamp' => $this->_getTimestamp(fread($this->_socket, 8)),
0224             'originatestamp' => $this->_getTimestamp(fread($this->_socket, 8)),
0225             'receivestamp'   => $this->_getTimestamp(fread($this->_socket, 8)),
0226             'transmitstamp'  => $this->_getTimestamp(fread($this->_socket, 8)),
0227             'clientreceived' => microtime(true)
0228         );
0229 
0230         $this->_disconnect();
0231         return $result;
0232     }
0233 
0234     /**
0235      * Sends the NTP packet to the server
0236      *
0237      * @param  string $data Data to send to the timeserver
0238      * @return void
0239      */
0240     protected function _write($data)
0241     {
0242         $this->_connect();
0243 
0244         fwrite($this->_socket, $data);
0245         stream_set_timeout($this->_socket, Zend_TimeSync::$options['timeout']);
0246     }
0247 
0248     /**
0249      * Extracts the binary data returned from the timeserver
0250      *
0251      * @param  string|array $binary Data returned from the timeserver
0252      * @return integer Difference in seconds
0253      */
0254     protected function _extract($binary)
0255     {
0256         /*
0257          * Leap Indicator bit 1100 0000
0258          *
0259          * Code warning of impending leap-second to be inserted at the end of
0260          * the last day of the current month.
0261          */
0262         $leap = ($binary['flags'] & 0xc0) >> 6;
0263         switch($leap) {
0264             case 0:
0265                 $this->_info['leap'] = '0 - no warning';
0266                 break;
0267 
0268             case 1:
0269                 $this->_info['leap'] = '1 - last minute has 61 seconds';
0270                 break;
0271 
0272             case 2:
0273                 $this->_info['leap'] = '2 - last minute has 59 seconds';
0274                 break;
0275 
0276             default:
0277                 $this->_info['leap'] = '3 - not syncronised';
0278                 break;
0279         }
0280 
0281         /*
0282          * Version Number bit 0011 1000
0283          *
0284          * This should be 3 (RFC 1305)
0285          */
0286         $this->_info['version'] = ($binary['flags'] & 0x38) >> 3;
0287 
0288         /*
0289          * Mode bit 0000 0111
0290          *
0291          * Except in broadcast mode, an NTP association is formed when two peers
0292          * exchange messages and one or both of them create and maintain an
0293          * instantiation of the protocol machine, called an association.
0294          */
0295         $mode = ($binary['flags'] & 0x07);
0296         switch($mode) {
0297             case 1:
0298                 $this->_info['mode'] = 'symetric active';
0299                 break;
0300 
0301             case 2:
0302                 $this->_info['mode'] = 'symetric passive';
0303                 break;
0304 
0305             case 3:
0306                 $this->_info['mode'] = 'client';
0307                 break;
0308 
0309             case 4:
0310                 $this->_info['mode'] = 'server';
0311                 break;
0312 
0313             case 5:
0314                 $this->_info['mode'] = 'broadcast';
0315                 break;
0316 
0317             default:
0318                 $this->_info['mode'] = 'reserved';
0319                 break;
0320         }
0321 
0322         $ntpserviceid = 'Unknown Stratum ' . $binary['stratum'] . ' Service';
0323 
0324         /*
0325          * Reference Clock Identifier
0326          *
0327          * Identifies the particular reference clock.
0328          */
0329         $refid = strtoupper($binary['referenceid']);
0330         switch($binary['stratum']) {
0331             case 0:
0332                 if (substr($refid, 0, 3) === 'DCN') {
0333                     $ntpserviceid = 'DCN routing protocol';
0334                 } else if (substr($refid, 0, 4) === 'NIST') {
0335                     $ntpserviceid = 'NIST public modem';
0336                 } else if (substr($refid, 0, 3) === 'TSP') {
0337                     $ntpserviceid = 'TSP time protocol';
0338                 } else if (substr($refid, 0, 3) === 'DTS') {
0339                     $ntpserviceid = 'Digital Time Service';
0340                 }
0341                 break;
0342 
0343             case 1:
0344                 if (substr($refid, 0, 4) === 'ATOM') {
0345                     $ntpserviceid = 'Atomic Clock (calibrated)';
0346                 } else if (substr($refid, 0, 3) === 'VLF') {
0347                     $ntpserviceid = 'VLF radio';
0348                 } else if ($refid === 'CALLSIGN') {
0349                     $ntpserviceid = 'Generic radio';
0350                 } else if (substr($refid, 0, 4) === 'LORC') {
0351                     $ntpserviceid = 'LORAN-C radionavigation';
0352                 } else if (substr($refid, 0, 4) === 'GOES') {
0353                     $ntpserviceid = 'GOES UHF environment satellite';
0354                 } else if (substr($refid, 0, 3) === 'GPS') {
0355                     $ntpserviceid = 'GPS UHF satellite positioning';
0356                 }
0357                 break;
0358 
0359             default:
0360                 $ntpserviceid  = ord(substr($binary['referenceid'], 0, 1));
0361                 $ntpserviceid .= '.';
0362                 $ntpserviceid .= ord(substr($binary['referenceid'], 1, 1));
0363                 $ntpserviceid .= '.';
0364                 $ntpserviceid .= ord(substr($binary['referenceid'], 2, 1));
0365                 $ntpserviceid .= '.';
0366                 $ntpserviceid .= ord(substr($binary['referenceid'], 3, 1));
0367                 break;
0368         }
0369 
0370         $this->_info['ntpid'] = $ntpserviceid;
0371 
0372         /*
0373          * Stratum
0374          *
0375          * Indicates the stratum level of the local clock
0376          */
0377         switch($binary['stratum']) {
0378             case 0:
0379                 $this->_info['stratum'] = 'undefined';
0380                 break;
0381 
0382             case 1:
0383                 $this->_info['stratum'] = 'primary reference';
0384                 break;
0385 
0386             default:
0387                 $this->_info['stratum'] = 'secondary reference';
0388                 break;
0389         }
0390 
0391         /*
0392          * Indicates the total roundtrip delay to the primary reference source at the
0393          * root of the synchronization subnet, in seconds.
0394          *
0395          * Both positive and negative values, depending on clock precision and skew, are
0396          * possible.
0397          */
0398         $this->_info['rootdelay'] = $binary['rootdelay'];
0399 
0400         /*
0401          * Indicates the maximum error relative to the primary reference source at the
0402          * root of the synchronization subnet, in seconds.
0403          *
0404          * Only positive values greater than zero are possible.
0405          */
0406         $this->_info['rootdispersion'] = $binary['rootdispersion'];
0407 
0408         /*
0409          * The roundtrip delay of the peer clock relative to the local clock
0410          * over the network path between them, in seconds.
0411          *
0412          * Note that this variable can take on both positive and negative values,
0413          * depending on clock precision and skew-error accumulation.
0414          */
0415         $this->_info['roundtrip']  = $binary['receivestamp'];
0416         $this->_info['roundtrip'] -= $binary['originatestamp'];
0417         $this->_info['roundtrip'] -= $binary['transmitstamp'];
0418         $this->_info['roundtrip'] += $binary['clientreceived'];
0419         $this->_info['roundtrip'] /= 2;
0420 
0421         // The offset of the peer clock relative to the local clock, in seconds.
0422         $this->_info['offset']  = $binary['receivestamp'];
0423         $this->_info['offset'] -= $binary['originatestamp'];
0424         $this->_info['offset'] += $binary['transmitstamp'];
0425         $this->_info['offset'] -= $binary['clientreceived'];
0426         $this->_info['offset'] /= 2;
0427         $time = (time() - $this->_info['offset']);
0428 
0429         return $time;
0430     }
0431 }