From 9cfe42e82ef384eeaa823ec1ababda3da9068f53 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Roland=20H=C3=A4der?= Date: Fri, 17 Aug 2012 20:11:29 +0000 Subject: [PATCH] Renamed exception, added new one --- .gitattributes | 3 + .../class_HubAlreadyAnnouncedException.php | 44 +---- application/hub/exceptions/node/.htaccess | 1 + .../class_NodeAlreadyAnnouncedException.php | 45 +++++ ...lass_NodeSessionIdVerficationException.php | 53 ++++++ .../class_HandleableAnswerStatus.php | 9 + .../hub/interfaces/nodes/class_NodeHelper.php | 2 +- application/hub/main/class_BaseHubSystem.php | 13 +- .../class_AnnouncementAnswerOkayHandler.php | 33 +++- .../class_BaseAnserStatusHandler.php | 11 ++ .../hub/main/handler/class_BaseHandler.php | 61 +++++- ...s_NodeMessageAnnouncementAnswerHandler.php | 7 +- .../network/class_BaseRawDataHandler.php | 179 +++++++++++++++++- .../hub/main/nodes/class_BaseHubNode.php | 4 +- 14 files changed, 405 insertions(+), 60 deletions(-) create mode 100644 application/hub/exceptions/node/.htaccess create mode 100644 application/hub/exceptions/node/class_NodeAlreadyAnnouncedException.php create mode 100644 application/hub/exceptions/node/class_NodeSessionIdVerficationException.php diff --git a/.gitattributes b/.gitattributes index 64ccc8722..ada260a72 100644 --- a/.gitattributes +++ b/.gitattributes @@ -21,6 +21,9 @@ application/hub/exceptions/lists/.htaccess -text svneol=unset#text/plain application/hub/exceptions/lists/class_InvalidListHashException.php svneol=native#text/plain application/hub/exceptions/lists/class_ListGroupAlreadyAddedException.php svneol=native#text/plain application/hub/exceptions/lists/class_NoListGroupException.php svneol=native#text/plain +application/hub/exceptions/node/.htaccess -text svneol=unset#text/plain +application/hub/exceptions/node/class_NodeAlreadyAnnouncedException.php svneol=native#text/plain +application/hub/exceptions/node/class_NodeSessionIdVerficationException.php svneol=native#text/plain application/hub/exceptions/package/.htaccess -text svneol=unset#text/plain application/hub/exceptions/package/class_FinalChunkVerificationException.php svneol=native#text/plain application/hub/exceptions/package/class_InvalidDataChecksumException.php svneol=native#text/plain diff --git a/application/hub/exceptions/hub/class_HubAlreadyAnnouncedException.php b/application/hub/exceptions/hub/class_HubAlreadyAnnouncedException.php index 03b3beacf..f551ef47b 100644 --- a/application/hub/exceptions/hub/class_HubAlreadyAnnouncedException.php +++ b/application/hub/exceptions/hub/class_HubAlreadyAnnouncedException.php @@ -1,45 +1,3 @@ - * @version 0.0.0 - * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2012 Hub Developer Team - * @license GNU GPL 3.0 or any newer version - * @link http://www.ship-simu.org - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -class HubAlreadyAnnouncedException extends FrameworkException { - /** - * The super constructor for all exceptions - * - * @param $messageArray Error message array - * @param $code Error code - * @return void - */ - public function __construct (NodeHelper $nodeInstance, $code) { - // Construct the message - $message = sprintf("[%s:%d] This hub is already announced. Please fix this.", - $nodeInstance->__toString(), - $this->getLine() - ); - - // Call parent exception constructor - parent::__construct($message, $code); - } -} - -// [EOF] +// @DEPRECATED ?> diff --git a/application/hub/exceptions/node/.htaccess b/application/hub/exceptions/node/.htaccess new file mode 100644 index 000000000..3a4288278 --- /dev/null +++ b/application/hub/exceptions/node/.htaccess @@ -0,0 +1 @@ +Deny from all diff --git a/application/hub/exceptions/node/class_NodeAlreadyAnnouncedException.php b/application/hub/exceptions/node/class_NodeAlreadyAnnouncedException.php new file mode 100644 index 000000000..c0b970438 --- /dev/null +++ b/application/hub/exceptions/node/class_NodeAlreadyAnnouncedException.php @@ -0,0 +1,45 @@ + + * @version 0.0.0 + * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2012 Hub Developer Team + * @license GNU GPL 3.0 or any newer version + * @link http://www.ship-simu.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +class NodeAlreadyAnnouncedException extends FrameworkException { + /** + * The super constructor for all exceptions + * + * @param $messageArray Error message array + * @param $code Error code + * @return void + */ + public function __construct (NodeHelper $nodeInstance, $code) { + // Construct the message + $message = sprintf("[%s:%d] This node is already announced. Please fix this.", + $nodeInstance->__toString(), + $this->getLine() + ); + + // Call parent exception constructor + parent::__construct($message, $code); + } +} + +// [EOF] +?> diff --git a/application/hub/exceptions/node/class_NodeSessionIdVerficationException.php b/application/hub/exceptions/node/class_NodeSessionIdVerficationException.php new file mode 100644 index 000000000..657fb8d56 --- /dev/null +++ b/application/hub/exceptions/node/class_NodeSessionIdVerficationException.php @@ -0,0 +1,53 @@ + + * @version 0.0.0 + * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2012 Hub Developer Team + * @license GNU GPL 3.0 or any newer version + * @link http://www.ship-simu.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +class NodeSessionIdVerficationException extends FrameworkException { + /** + * The super constructor for all exceptions + * + * @param $messageArray Error message array + * @param $code Error code + * @return void + */ + public function __construct (array $messageArray, $code) { + // Construct the message + $message = sprintf("[%s:%d] Session id %s cannot be verfied. ip=%s/%s,tcp.port=%s,udp.port=%s,status=%s.", + $messageArray[0]->__toString(), + $this->getLine(), + $messageArray[1][XmlAnnouncementAnswerTemplateEngine::ANNOUNCEMENT_DATA_SESSION_ID], + $messageArray[1][XmlAnnouncementAnswerTemplateEngine::ANNOUNCEMENT_DATA_EXTERNAL_IP], + $messageArray[1][XmlAnnouncementAnswerTemplateEngine::ANNOUNCEMENT_DATA_INTERNAL_IP], + $messageArray[1][XmlAnnouncementAnswerTemplateEngine::ANNOUNCEMENT_DATA_TCP_PORT], + $messageArray[1][XmlAnnouncementAnswerTemplateEngine::ANNOUNCEMENT_DATA_UDP_PORT], + $messageArray[1][XmlAnnouncementAnswerTemplateEngine::ANNOUNCEMENT_DATA_NODE_STATUS] + ); + + // Call parent exception constructor + parent::__construct($message, $code); + } +} + +// [EOF] +?> diff --git a/application/hub/interfaces/handler/answer-status/class_HandleableAnswerStatus.php b/application/hub/interfaces/handler/answer-status/class_HandleableAnswerStatus.php index 6044e868c..ac5e2658c 100644 --- a/application/hub/interfaces/handler/answer-status/class_HandleableAnswerStatus.php +++ b/application/hub/interfaces/handler/answer-status/class_HandleableAnswerStatus.php @@ -29,6 +29,15 @@ interface HandleableAnswerStatus extends Handleable { * @return void */ function handleAnswerMessageData (array $messageData); + + /** + * Adds all required elements from given array into data set instance + * + * @param $dataSetInstance An instance of a StoreableCriteria class + * @param $messageData An array with all message data + * @return void + */ + function addArrayToDataSet (StoreableCriteria $dataSetInstance, array $messageData); } // [EOF] diff --git a/application/hub/interfaces/nodes/class_NodeHelper.php b/application/hub/interfaces/nodes/class_NodeHelper.php index f3c87c29d..763f88a6c 100644 --- a/application/hub/interfaces/nodes/class_NodeHelper.php +++ b/application/hub/interfaces/nodes/class_NodeHelper.php @@ -109,7 +109,7 @@ interface NodeHelper extends FrameworkInterface { * * @param $taskInstance The task instance running this announcement * @return void - * @throws HubAlreadyAnnouncedException If this hub is already announced + * @throws NodeAlreadyAnnouncedException If this hub is already announced */ function announceSelfToUpperNodes (Taskable $taskInstance); diff --git a/application/hub/main/class_BaseHubSystem.php b/application/hub/main/class_BaseHubSystem.php index 391e01489..d801735f5 100644 --- a/application/hub/main/class_BaseHubSystem.php +++ b/application/hub/main/class_BaseHubSystem.php @@ -23,12 +23,13 @@ */ class BaseHubSystem extends BaseFrameworkSystem { // Exception codes - const EXCEPTION_UNSUPPORTED_ERROR_HANDLER = 0x900; - const EXCEPTION_CHUNK_ALREADY_ASSEMBLED = 0x901; - const EXCEPTION_ANNOUNCEMENT_NOT_ACCEPTED = 0x902; - const EXCEPTION_INVALID_CONNECTION_TYPE = 0x903; - const EXCEPTION_ANNOUNCEMENT_NOT_ATTEMPTED = 0x904; - const EXCEPTION_BASE64_ENCODING_NOT_MODULO_4 = 0x905; + const EXCEPTION_UNSUPPORTED_ERROR_HANDLER = 0x900; + const EXCEPTION_CHUNK_ALREADY_ASSEMBLED = 0x901; + const EXCEPTION_ANNOUNCEMENT_NOT_ACCEPTED = 0x902; + const EXCEPTION_INVALID_CONNECTION_TYPE = 0x903; + const EXCEPTION_ANNOUNCEMENT_NOT_ATTEMPTED = 0x904; + const EXCEPTION_BASE64_ENCODING_NOT_MODULO_4 = 0x905; + const EXCEPTION_NODE_SESSION_ID_NOT_VERIFYING = 0x906; // Message status codes const MESSAGE_STATUS_CODE_OKAY = 'OKAY'; diff --git a/application/hub/main/handler/answer-status/announcement/class_AnnouncementAnswerOkayHandler.php b/application/hub/main/handler/answer-status/announcement/class_AnnouncementAnswerOkayHandler.php index d9cc21b23..5be1a0abc 100644 --- a/application/hub/main/handler/answer-status/announcement/class_AnnouncementAnswerOkayHandler.php +++ b/application/hub/main/handler/answer-status/announcement/class_AnnouncementAnswerOkayHandler.php @@ -51,11 +51,36 @@ class AnnouncementAnswerOkayHandler extends BaseAnserStatusHandler implements Ha /** * Handles given message data array * - * @param $messageData An array of message data - * @return void - */ + * @param $messageData An array of message data + * @return void + * @throws NodeSessionIdVerficationException If the provided session id is not matching + */ public function handleAnswerMessageData (array $messageData) { - $this->partialStub('Please implement this method.'); + // Get a database wrapper instance + $wrapperInstance = ObjectFactory::createObjectByConfiguredName('node_list_db_wrapper_class'); + + // Get also a search criteria instance + $searchInstance = ObjectFactory::createObjectByConfiguredName('search_criteria_class'); + + // Lookup external session id/external IP/port + $searchInstance->addCriteria('node_session_id' , $messageData[XmlAnnouncementAnswerTemplateEngine::ANNOUNCEMENT_DATA_SESSION_ID]); + $searchInstance->addCriteria('node_external_ip', $messageData[XmlAnnouncementAnswerTemplateEngine::ANNOUNCEMENT_DATA_EXTERNAL_IP]); + $searchInstance->addCriteria('node_tcp_port' , $messageData[XmlAnnouncementAnswerTemplateEngine::ANNOUNCEMENT_DATA_TCP_PORT]); + + // Only one entry is fine + $searchInstance->setLimit(1); + + // Run the query + $resultInstance = $wrapperInstance->doSelectByCriteria($searchInstance); + + // Is there an try? + if (!$resultInstance->next()) { + // This is fatal, caused by "stolen" session id and/or not matching IP number/port combination + throw new NodeSessionIdVerficationException(array($this, $messageData), BaseHubSystem::EXCEPTION_NODE_SESSION_ID_NOT_VERIFYING); + } // END - if + + // Update node data (include status code) + $wrapperInstance->updateNodeByMessageData($messageData, $this, $searchInstance); } } diff --git a/application/hub/main/handler/answer-status/class_BaseAnserStatusHandler.php b/application/hub/main/handler/answer-status/class_BaseAnserStatusHandler.php index 4c1a6efb5..93d24ed21 100644 --- a/application/hub/main/handler/answer-status/class_BaseAnserStatusHandler.php +++ b/application/hub/main/handler/answer-status/class_BaseAnserStatusHandler.php @@ -57,6 +57,17 @@ class BaseAnserStatusHandler extends BaseHandler { protected final function getLastException () { return $this->lastException; } + + /** + * Adds all required elements from given array into data set instance + * + * @param $dataSetInstance An instance of a StoreableCriteria class + * @param $messageData An array with all message data + * @return void + */ + public function addArrayToDataSet (StoreableCriteria $dataSetInstance, array $messageData) { + die(__METHOD__.chr(10)); + } } // [EOF] diff --git a/application/hub/main/handler/class_BaseHandler.php b/application/hub/main/handler/class_BaseHandler.php index f551ef47b..aebb05625 100644 --- a/application/hub/main/handler/class_BaseHandler.php +++ b/application/hub/main/handler/class_BaseHandler.php @@ -1,3 +1,62 @@ + * @version 0.0.0 + * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2012 Hub Developer Team + * @license GNU GPL 3.0 or any newer version + * @link http://www.ship-simu.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +class BaseHandler extends BaseHubSystem { + /** + * Handler name + */ + private $handlerName = 'invalid'; + + /** + * Protected constructor + * + * @param $className Name of the class + * @return void + */ + protected function __construct ($className) { + // Call parent constructor + parent::__construct($className); + } + + /** + * Getter for handler name + * + * @return $handlerName Name of this handler + */ + public final function getHandlerName () { + return $this->handlerName; + } + + /** + * Setter for handler name + * + * @param $handlerName Name of this handler + * @return void + */ + protected final function setHandlerName ($handlerName) { + $this->handlerName = $handlerName; + } +} + +// [EOF] ?> diff --git a/application/hub/main/handler/message-types/answer/class_NodeMessageAnnouncementAnswerHandler.php b/application/hub/main/handler/message-types/answer/class_NodeMessageAnnouncementAnswerHandler.php index df124db2f..2f6f7a5a7 100644 --- a/application/hub/main/handler/message-types/answer/class_NodeMessageAnnouncementAnswerHandler.php +++ b/application/hub/main/handler/message-types/answer/class_NodeMessageAnnouncementAnswerHandler.php @@ -125,8 +125,11 @@ class NodeMessageAnnouncementAnswerHandler extends BaseMessageHandler implements // Is it there? assert(isset($messageData[$key])); - // Add it - $dataSetInstance->addCriteria('node_' . $key, $messageData[$key]); + /* + * Add it, but remove any 'my-' prefixes as they are not used in + * database layer. + */ + $dataSetInstance->addCriteria('node_' . str_replace('my-', '', $key), $messageData[$key]); } // END - foreach } diff --git a/application/hub/main/handler/network/class_BaseRawDataHandler.php b/application/hub/main/handler/network/class_BaseRawDataHandler.php index f551ef47b..96147ecb3 100644 --- a/application/hub/main/handler/network/class_BaseRawDataHandler.php +++ b/application/hub/main/handler/network/class_BaseRawDataHandler.php @@ -1,3 +1,180 @@ + * @version 0.0.0 + * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2012 Hub Developer Team + * @license GNU GPL 3.0 or any newer version + * @link http://www.ship-simu.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +class BaseRawDataHandler extends BaseHandler { + // Error codes: + // - Socket raw data stream errors + const SOCKET_ERROR_UNKNOWN = 'unknown_error'; // Unknown error (should not happen) + const SOCKET_ERROR_TRANSPORT_ENDPOINT = 'transport_endpoint'; // Transport endpoint has closed + const SOCKET_ERROR_INVALID_BASE64_MODULO = 'base64_modulo'; // Length is not modulo 4 + const SOCKET_ERROR_INVALID_BASE64_MESSAGE = 'base64_message'; // Raw data is not Base64-encoded + const SOCKET_ERROR_UNHANDLED = 'unhandled_package'; // Unhandled raw data (not bad) + const SOCKET_ERROR_CONNECTION_REFUSED = 'connection_refused'; // The name says it: connection refused + const SOCKET_ERROR_CONNECTION_TIMED_OUT = 'connection_timed_out'; // The name says it: connection attempt has timed-out + const SOCKET_ERROR_OPERATION_IN_PROGRESS = 'operation_in_progress'; // 'Operation now in progress' + const SOCKET_ERROR_OPERATION_ALREADY_PROGRESS = 'operation_already_progress'; // 'Operation already in progress' + const SOCKET_ERROR_RESOURCE_UNAVAILABLE = 'resource_unavailable'; // 'Resource temporary unavailable' + const SOCKET_ERROR_NO_ROUTE_TO_HOST = 'no_route_to_host'; // The name says it: no route to host + const SOCKET_CONNECTED = 'connected'; // Nothing errorous happens, socket is connected + + // - Package errors + const PACKAGE_ERROR_INVALID_DATA = 'invalid_data'; // Invalid data in package found + const PACKAGE_ERROR_INCOMPLETE_DATA = 'incomplete_data'; // Incomplete data sent (e.g. field is missing) + const PACKAGE_ERROR_INVALID_CONTENT = 'invalid_content'; // Content is invalid (e.g. not well-formed) + const PACKAGE_ERROR_RECIPIENT_MISMATCH = 'recipient_error'; // Recipient is not us + const PACKAGE_LEVEL_CHECK_OKAY = 'checked_package'; // Package is fine + + // Package data + const PACKAGE_RAW_DATA = 'raw_data'; + const PACKAGE_ERROR_CODE = 'error_code'; + + // Start/end marker + const STREAM_START_MARKER = '[[S]]'; + const STREAM_END_MARKER = '[[E]]'; + + /** + * Stacker for raw data + */ + const STACKER_NAME_RAW_DATA = 'raw_data'; + + /** + * Error code from socket + */ + private $errorCode = -1; + + /** + * Protected constructor + * + * @param $className Name of the class + * @return void + */ + protected function __construct ($className) { + // Call parent constructor + parent::__construct($className); + + // Set error code to 'unknown' + $this->setErrorCode(self::SOCKET_ERROR_UNKNOWN); + + // Init stacker instance for processed raw data + $stackerInstance = ObjectFactory::createObjectByConfiguredName('node_raw_data_stacker_class'); + + // Remember this in this package handler + $this->setStackerInstance($stackerInstance); + + // Init stacker + $this->initStacker(); + } + + /** + * Initializes the stacker for raw data + * + * @return void + */ + protected function initStacker () { + $this->getStackerInstance()->initStacker(self::STACKER_NAME_RAW_DATA); + } + + /** + * Adds given raw data to the raw data stacker + * + * @param $rawData raw data from the socket resource + * @return void + */ + protected function addRawDataToStacker ($rawData) { + /* + * Add the deocoded data and error code to the stacker so other classes + * (e.g. NetworkPackage) can "pop" it from the stacker. + */ + $this->getStackerInstance()->pushNamed(self::STACKER_NAME_RAW_DATA, array( + self::PACKAGE_RAW_DATA => $rawData, + self::PACKAGE_ERROR_CODE => $this->getErrorCode() + )); + } + + /** + * Checks whether raw data is pending for further processing. + * + * @return $isPending Whether raw data is pending + */ + public function isRawDataPending () { + // Does the stacker have some entries (not empty)? + $isPending = (!$this->getStackerInstance()->isStackEmpty(self::STACKER_NAME_RAW_DATA)); + + // Return it + return $isPending; + } + + /** + * "Getter" for next raw data from the stacker + * + * @return $rawData Raw data from the stacker + */ + public function getNextRawData () { + // "Pop" the raw data from the stacker + $rawData = $this->getStackerInstance()->popNamed(self::STACKER_NAME_RAW_DATA); + + // And return it + return $rawData; + } + + /** + * Checks whether the 'recipient' field matches our own address:port + * combination. + * + * @param $packageData Raw package data + * @return $matches Whether it matches + * @todo This method will be moved to a better place + */ + protected function ifRecipientMatchesOwnAddress (array $packageData) { + // Construct own address first + $ownAddress = Registry::getRegistry()->getInstance('node')->getAddressPort($this); + + // Does it match? + $matches = ($ownAddress === $packageData[NetworkPackage::PACKAGE_DATA_RECIPIENT]); + + // Return result + return $matches; + } + + /** + * Setter for error code + * + * @param $errorCode The error code we shall set + * @return void + */ + public final function setErrorCode ($errorCode) { + $this->errorCode = $errorCode; + } + + /** + * Getter for error code + * + * @return $errorCode The error code + */ + public final function getErrorCode () { + return $this->errorCode; + } +} + +// [EOF] ?> diff --git a/application/hub/main/nodes/class_BaseHubNode.php b/application/hub/main/nodes/class_BaseHubNode.php index 3a2be5ce3..386980413 100644 --- a/application/hub/main/nodes/class_BaseHubNode.php +++ b/application/hub/main/nodes/class_BaseHubNode.php @@ -481,14 +481,14 @@ class BaseHubNode extends BaseHubSystem implements Updateable { * * @param $taskInstance The task instance running this announcement * @return void - * @throws HubAlreadyAnnouncedException If this hub is already announced + * @throws NodeAlreadyAnnouncedException If this hub is already announced * @todo Change the first if() block to check for a specific state */ public function announceSelfToUpperNodes (Taskable $taskInstance) { // Is this hub node announced? if ($this->hubIsAnnounced === true) { // Already announced! - throw new HubAlreadyAnnouncedException($this, self::EXCEPTION_HUB_ALREADY_ANNOUNCED); + throw new NodeAlreadyAnnouncedException($this, self::EXCEPTION_HUB_ALREADY_ANNOUNCED); } // END - if // Debug output -- 2.39.5