From 08802dcb928d794496be419fffa85825eced7c21 Mon Sep 17 00:00:00 2001 From: "Michael J. Rubinsky" Date: Sun, 18 Apr 2010 12:37:57 -0400 Subject: [PATCH] Add configurabel heartbeat intervals --- .../lib/Horde/ActiveSync/Driver/Base.php | 10 +++ .../lib/Horde/ActiveSync/Request/Ping.php | 78 +++++++++++++++++----- .../ActiveSync/lib/Horde/ActiveSync/State/File.php | 15 +++-- .../lib/Horde/ActiveSync/State/History.php | 4 +- .../ActiveSync/lib/Horde/ActiveSync/Wbxml.php | 2 +- horde/config/conf.xml | 23 +++++++ horde/rpc.php | 4 +- 7 files changed, 109 insertions(+), 27 deletions(-) diff --git a/framework/ActiveSync/lib/Horde/ActiveSync/Driver/Base.php b/framework/ActiveSync/lib/Horde/ActiveSync/Driver/Base.php index 3e5cf0674..7c2d45137 100644 --- a/framework/ActiveSync/lib/Horde/ActiveSync/Driver/Base.php +++ b/framework/ActiveSync/lib/Horde/ActiveSync/Driver/Base.php @@ -164,6 +164,16 @@ abstract class Horde_ActiveSync_Driver_Base } /** + * Obtain the ping heartbeat settings + * + * @return array + */ + public function getHeartbeatConfig() + { + return $this->_params['ping']; + } + + /** * Get folder stat * "id" => The server ID that will be used to identify the folder. * It must be unique, and not too long. How long exactly is not diff --git a/framework/ActiveSync/lib/Horde/ActiveSync/Request/Ping.php b/framework/ActiveSync/lib/Horde/ActiveSync/Request/Ping.php index 38afc26b8..436477c89 100644 --- a/framework/ActiveSync/lib/Horde/ActiveSync/Request/Ping.php +++ b/framework/ActiveSync/lib/Horde/ActiveSync/Request/Ping.php @@ -31,7 +31,7 @@ class Horde_ActiveSync_Request_Ping extends Horde_ActiveSync_Request_Base // Ping const PING = 'Ping:Ping'; const STATUS = 'Ping:Status'; - const LIFETIME = 'Ping:LifeTime'; + const HEARTBEATINTERVAL = 'Ping:HeartbeatInterval'; const FOLDERS = 'Ping:Folders'; const FOLDER = 'Ping:Folder'; const SERVERENTRYID = 'Ping:ServerEntryId'; @@ -50,11 +50,15 @@ class Horde_ActiveSync_Request_Ping extends Horde_ActiveSync_Request_Base */ public function handle(Horde_ActiveSync $activeSync, $devId) { + $now = time(); parent::handle($activeSync, $devId); - // FIXME - $timeout = 3; - $this->_logger->info('[' . $devId . '] Ping received.'); + /* Get the settings for the server */ + $ping_settings = $this->_driver->getHeartbeatConfig(); + $timeout = $ping_settings['waitinterval']; + + /* Notify */ + $this->_logger->info('[' . $devId . '] Ping received at timestamp: ' . $now . '.'); /* Glass half full kinda guy... */ $this->_statusCode = self::STATUS_NOCHANGES; @@ -66,13 +70,39 @@ class Horde_ActiveSync_Request_Ping extends Horde_ActiveSync_Request_Base * we read in the PING request since the PING request is allowed to omit * sections if they have been sent previously */ $collections = array_values($state->initPingState($this->_devId)); - $lifetime = $state->getPingLifetime(); + $lifetime = $state->getHeartbeatInterval(); + if ($lifetime !== 0 && $lifetime < $ping_settings['heartbeatmin']) { + $this->_statusCode = self::STATUS_HBOUTOFBOUNDS; + $lifetime = $ping_settings['heartbeatmin']; + } elseif ($lifetime > $ping_settings['heartbeatmax']) { + $this->_statusCode = self::STATUS_HBOUTOFBOUNDS; + $lifetime = $ping_settings['heartbeatmax']; + } + if ($this->_statusCode == self::STATUS_HBOUTOFBOUNDS) { + $state->resetPingState(); + $this->_handleHBError($lifetime); + $state->savePingState(); + return false; + } /* Build the $collections array if we receive request from PIM */ if ($this->_decoder->getElementStartTag(self::PING)) { - if ($this->_decoder->getElementStartTag(self::LIFETIME)) { + if ($this->_decoder->getElementStartTag(self::HEARTBEATINTERVAL)) { $lifetime = $this->_decoder->getElementContent(); - $state->setPingLifetime($lifetime); + if ($lifetime !== 0 && $lifetime < $ping_settings['heartbeatmin']) { + $this->_statusCode = self::STATUS_HBOUTOFBOUNDS; + $lifetime = $ping_settings['heartbeatmin']; + } elseif ($lifetime > $ping_settings['heartbeatmax']) { + $this->_statusCode = self::STATUS_HBOUTOFBOUNDS; + $lifetime = $ping_settings['heartbeatmax']; + } + if ($this->_statusCode == self::STATUS_HBOUTOFBOUNDS) { + $state->resetPingState(); + $this->_handleHBError($lifetime); + $state->savePingState(); + return false; + } + $state->setHeartbeatInterval($lifetime); $this->_decoder->getElementEndTag(); } @@ -110,11 +140,11 @@ class Horde_ActiveSync_Request_Ping extends Horde_ActiveSync_Request_Base /* Start waiting for changes, but only if we don't have any errors */ if ($this->_statusCode == self::STATUS_NOCHANGES) { - $this->_logger->info(sprintf('[%s] Waiting for changes (lifetime: %d)', $this->_devId, $lifetime)); - // FIXME - //for ($n = 0; $n < $lifetime / $timeout; $n++) { - for ($n = 0; $n < 10; $n++) { - //check the remote wipe status + $this->_logger->info(sprintf('[%s] Waiting for changes (heartbeat interval: %d)', $this->_devId, $lifetime)); + $expire = $now + $lifetime; + while (time() <= $expire) { + /* Check the remote wipe status and request a foldersync if + * we want the device wiped. */ if ($this->_provisioning === true) { $rwstatus = $state->getDeviceRWStatus($this->_devId); if ($rwstatus == Horde_ActiveSync::PROVISION_RWSTATUS_PENDING || $rwstatus == Horde_ActiveSync::PROVISION_RWSTATUS_WIPED) { @@ -131,8 +161,6 @@ class Horde_ActiveSync_Request_Ping extends Horde_ActiveSync_Request_Base for ($i = 0; $i < count($collections); $i++) { $collection = $collections[$i]; - // Make sure we have the synckey (which is the devid for - // PING requests. $collection['synckey'] = $this->_devId; $sync = $this->_driver->getSyncObject(); $state->loadPingCollectionState($collection); @@ -141,7 +169,6 @@ class Horde_ActiveSync_Request_Ping extends Horde_ActiveSync_Request_Base } catch (Horde_ActiveSync_Exception $e) { /* Stop ping if exporter cannot be configured */ $this->_logger->err('Ping error: Exporter can not be configured. Waiting 30 seconds before ping is retried.'); - $n = $lifetime/ $timeout; sleep(30); break; } @@ -153,8 +180,8 @@ class Horde_ActiveSync_Request_Ping extends Horde_ActiveSync_Request_Base $this->_statusCode = self::STATUS_NEEDSYNC; } - // Update the state, but don't bother with the backend since we - // are not updating any data. + /* Update the state, but don't bother with the backend since we + * are not updating any data.*/ while (is_array($sync->syncronize(Horde_ActiveSync::BACKEND_DISCARD_DATA))); } @@ -162,9 +189,12 @@ class Horde_ActiveSync_Request_Ping extends Horde_ActiveSync_Request_Base $this->_logger->info('[' . $this->_devId . '] Changes available'); break; } + /* Wait a bit before trying again */ sleep($timeout); } } + + /* Prepare for response */ $this->_logger->info('[' . $this->_devId . '] Sending response for PING.'); $this->_encoder->StartWBXML(); @@ -190,4 +220,18 @@ class Horde_ActiveSync_Request_Ping extends Horde_ActiveSync_Request_Base return true; } + + protected function _handleHBError($lifetime) + { + $this->_logger->info('[' . $this->_devId . '] Sending response for PING.'); + $this->_encoder->StartWBXML(); + $this->_encoder->startTag(self::PING); + $this->_encoder->startTag(self::STATUS); + $this->_encoder->content($this->_statusCode); + $this->_encoder->endTag(); + $this->_encoder->startTag(self::HEARTBEATINTERVAL); + $this->_encoder->content($lifetime); + $this->_encoder->endTag(); + $this->_encoder->endTag(); + } } diff --git a/framework/ActiveSync/lib/Horde/ActiveSync/State/File.php b/framework/ActiveSync/lib/Horde/ActiveSync/State/File.php index 5dca9d0e2..fd0f06f81 100644 --- a/framework/ActiveSync/lib/Horde/ActiveSync/State/File.php +++ b/framework/ActiveSync/lib/Horde/ActiveSync/State/File.php @@ -334,14 +334,19 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base if (file_exists($file)) { $this->_pingState = unserialize(file_get_contents($file)); } else { - $this->_pingState = array( - 'lifetime' => 0, - 'collections' => array()); + $this->resetPingState(); } return $this->_pingState['collections']; } + public function resetPingState() + { + $this->_pingState = array( + 'lifetime' => 0, + 'collections' => array()); + } + /** * Obtain the device object. * @@ -490,7 +495,7 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base * @return integer The hearbeat interval, or zero if not found. * @throws Horde_ActiveSync_Exception */ - public function getPingLifetime() + public function getHeartbeatInterval() { if (empty($this->_pingState)) { throw new Horde_ActiveSync_Exception('PING state not initialized'); @@ -499,7 +504,7 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base return (!$this->_pingState) ? 0 : $this->_pingState['lifetime']; } - public function setPingLifetime($lifetime) + public function setHeartbeatInterval($lifetime) { $this->_pingState['lifetime'] = $lifetime; } diff --git a/framework/ActiveSync/lib/Horde/ActiveSync/State/History.php b/framework/ActiveSync/lib/Horde/ActiveSync/State/History.php index 41e9cde0c..e5e4d096c 100644 --- a/framework/ActiveSync/lib/Horde/ActiveSync/State/History.php +++ b/framework/ActiveSync/lib/Horde/ActiveSync/State/History.php @@ -395,7 +395,7 @@ class Horde_ActiveSync_State_History extends Horde_ActiveSync_State_Base * @return integer The hearbeat interval, or zero if not found. * @throws Horde_ActiveSync_Exception */ - public function getPingLifetime() + public function getHeartbeatInterval() { if (empty($this->_pingState)) { throw new Horde_ActiveSync_Exception('PING state not initialized'); @@ -404,7 +404,7 @@ class Horde_ActiveSync_State_History extends Horde_ActiveSync_State_Base return (!$this->_pingState) ? 0 : $this->_pingState['lifetime']; } - public function setPingLifetime($lifetime) + public function setHeartbeatInterval($lifetime) { $this->_pingState['lifetime'] = $lifetime; } diff --git a/framework/ActiveSync/lib/Horde/ActiveSync/Wbxml.php b/framework/ActiveSync/lib/Horde/ActiveSync/Wbxml.php index 4235a6ae3..a3a45834b 100644 --- a/framework/ActiveSync/lib/Horde/ActiveSync/Wbxml.php +++ b/framework/ActiveSync/lib/Horde/ActiveSync/Wbxml.php @@ -407,7 +407,7 @@ class Horde_ActiveSync_Wbxml 0xd => array ( 0x05 => 'Ping', 0x07 => 'Status', - 0x08 => 'LifeTime', + 0x08 => 'HeartbeatInterval', 0x09 => 'Folders', 0x0a => 'Folder', 0x0b => 'ServerEntryId', diff --git a/horde/config/conf.xml b/horde/config/conf.xml index b9af17a82..68cf70986 100644 --- a/horde/config/conf.xml +++ b/horde/config/conf.xml @@ -1991,6 +1991,29 @@ + + Ping Settings + The Heartbeat Interval specifies the length of time, in + seconds, that the server SHOULD wait before notifying the client of + changes in a folder on the server. These settings allow you to indicate + the allowable range for this setting. Note that not all clients will + request this information. + 10 + + 60 + 10 + + 5 + Security Policies