aboutsummaryrefslogtreecommitdiffstats
path: root/ucs2-lib/src/ucs_epm.c
diff options
context:
space:
mode:
authorFulup Ar Foll <fulup@iot.bzh>2017-05-26 18:45:56 +0200
committerFulup Ar Foll <fulup@iot.bzh>2017-05-26 18:45:56 +0200
commitd2e42029ec04c3f224580f8007cdfbbfe0fc47a6 (patch)
treead2ccf167cf7997c84191d41e6ba55cb2efd6bed /ucs2-lib/src/ucs_epm.c
parent18e393e1443fd4c38b34979888fb55d30448cf31 (diff)
Initial Commit
Diffstat (limited to 'ucs2-lib/src/ucs_epm.c')
-rw-r--r--ucs2-lib/src/ucs_epm.c495
1 files changed, 495 insertions, 0 deletions
diff --git a/ucs2-lib/src/ucs_epm.c b/ucs2-lib/src/ucs_epm.c
new file mode 100644
index 0000000..adc5aab
--- /dev/null
+++ b/ucs2-lib/src/ucs_epm.c
@@ -0,0 +1,495 @@
+/*------------------------------------------------------------------------------------------------*/
+/* UNICENS V2.1.0-3491 */
+/* Copyright (c) 2017 Microchip Technology Germany II GmbH & Co. KG. */
+/* */
+/* 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 2 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 <http://www.gnu.org/licenses/>. */
+/* */
+/* You may also obtain this software under a propriety license from Microchip. */
+/* Please contact Microchip for further information. */
+/*------------------------------------------------------------------------------------------------*/
+
+/*!
+ * \file
+ * \brief Implementation of the EndPoint Management.
+ *
+ * \cond UCS_INTERNAL_DOC
+ * \addtogroup G_EPM
+ * @{
+ */
+
+/*------------------------------------------------------------------------------------------------*/
+/* Includes */
+/*------------------------------------------------------------------------------------------------*/
+#include "ucs_epm.h"
+#include "ucs_misc.h"
+
+/*------------------------------------------------------------------------------------------------*/
+/* Internal prototypes */
+/*------------------------------------------------------------------------------------------------*/
+static void Epm_XrmReportCb (uint16_t node_address, uint16_t connection_label, Ucs_Xrm_Result_t result, void * user_arg);
+static bool Epm_RsmReportSyncLost (Fac_Inst_t inst_type, void * inst_ptr, void *ud_ptr);
+
+/*------------------------------------------------------------------------------------------------*/
+/* Implementation of class CEndpointManagement */
+/*------------------------------------------------------------------------------------------------*/
+/*------------------------------------------------------------------------------------------------*/
+/* Initialization Methods */
+/*------------------------------------------------------------------------------------------------*/
+/*! \brief Constructor of the Remote Sync Manager class.
+ * \param self Instance pointer
+ * \param init_ptr init data_ptr
+ */
+void Epm_Ctor(CEndpointManagement *self, Epm_InitData_t *init_ptr)
+{
+ MISC_MEM_SET(self, 0, sizeof(CEndpointManagement));
+
+ /* Init all instances */
+ self->fac_ptr = init_ptr->fac_ptr;
+ self->base_ptr = init_ptr->base_ptr;
+ self->res_debugging_fptr = init_ptr->res_debugging_fptr;
+ self->check_unmute_fptr = init_ptr->check_unmute_fptr;
+}
+
+/*! \brief Initializes the internal information of the given endpoint object.
+ *
+ * Initialization is performed only if the magic number is not set.
+ *
+ * \param self Instance pointer
+ * \param ep_ptr Reference to the endpoint to be looked for
+ */
+void Epm_InitInternalInfos(CEndpointManagement * self, Ucs_Rm_EndPoint_t * ep_ptr)
+{
+ if ((self != NULL) && (ep_ptr != NULL))
+ {
+ if (ep_ptr->internal_infos.magic_number != (uint32_t)0x0BADC0DE)
+ {
+ MISC_MEM_SET(&ep_ptr->internal_infos, 0, sizeof(Ucs_Rm_EndPointInt_t));
+
+ ep_ptr->internal_infos.magic_number = (uint32_t)0x0BADC0DE;
+ Sub_Ctor(&ep_ptr->internal_infos.subject_obj, self->base_ptr->ucs_user_ptr);
+ /* Set the EndpointManagement instance */
+ ep_ptr->internal_infos.epm_inst = (Epm_Inst_t *)(void *)self;
+ }
+ }
+}
+
+/*! \brief Clears the internal information of the given endpoint object.
+ *
+ * Resetting the magic number of the given endpoint will enforce Its Re-Initialization.
+ *
+ * \param self Instance pointer
+ * \param ep_ptr Reference to the endpoint to be cleared.
+ */
+void Epm_ClearIntInfos(CEndpointManagement * self, Ucs_Rm_EndPoint_t * ep_ptr)
+{
+ MISC_UNUSED (self);
+ if (ep_ptr != NULL)
+ {
+ ep_ptr->internal_infos.magic_number = 0x0U;
+ }
+}
+
+/*------------------------------------------------------------------------------------------------*/
+/* Service */
+/*------------------------------------------------------------------------------------------------*/
+/*! \brief Add an observer to the Endpoint's subject.
+ * \param ep_ptr Reference to the endpoint instance
+ * \param obs_ptr Reference to the observer object
+ */
+void Epm_AddObserver(Ucs_Rm_EndPoint_t * ep_ptr, CObserver * obs_ptr)
+{
+ Sub_Ret_t ret_val = SUB_UNKNOWN_OBSERVER;
+
+ ret_val = Sub_AddObserver(&ep_ptr->internal_infos.subject_obj, obs_ptr);
+ if (ret_val == SUB_OK)
+ {
+ if ((ep_ptr != NULL) && (ep_ptr->endpoint_type == UCS_RM_EP_SOURCE))
+ {
+ if ((ep_ptr->internal_infos.endpoint_state == UCS_RM_EP_BUILT) && (ep_ptr->internal_infos.reference_cnt > 0U))
+ {
+ ep_ptr->internal_infos.reference_cnt++;
+ }
+ }
+ }
+}
+
+/*! \brief Removes an observer registered by Epm_AddObserver
+ * \param ep_ptr Reference to the endpoint instance
+ * \param obs_ptr Reference to the observer object
+ */
+void Epm_DelObserver(Ucs_Rm_EndPoint_t * ep_ptr, CObserver * obs_ptr)
+{
+ (void)Sub_RemoveObserver(&ep_ptr->internal_infos.subject_obj, obs_ptr);
+}
+
+/*! \brief Processes the construction of the given endpoint
+ * \param self Instance pointer
+ * \param ep_ptr reference to an endpoint
+ * \return Possible return values are
+ * - \c UCS_RET_ERR_API_LOCKED the API is locked. Endpoint is currently being processed.
+ * - \c UCS_RET_SUCCESS the build process was set successfully
+ * - \c UCS_RET_ERR_PARAM NULL pointer detected in the parameter list
+ * - \c UCS_RET_ERR_ALREADY_SET the endpoint has already been set
+ */
+Ucs_Return_t Epm_SetBuildProcess(CEndpointManagement * self, Ucs_Rm_EndPoint_t * ep_ptr)
+{
+ Ucs_Return_t result = UCS_RET_ERR_PARAM;
+
+ if ((self != NULL) && (ep_ptr != NULL))
+ {
+ /* Process Endpoint construction by XRM */
+ result = Xrm_Process(Fac_GetXrm(self->fac_ptr, ep_ptr->node_obj_ptr->signature_ptr->node_address, &Epm_XrmResDebugCb, self->check_unmute_fptr),
+ ep_ptr->jobs_list_ptr, ep_ptr->internal_infos.connection_label,
+ (void *)ep_ptr, &Epm_XrmReportCb);
+ if (result == UCS_RET_SUCCESS)
+ {
+ if (ep_ptr->internal_infos.endpoint_state != UCS_RM_EP_BUILT)
+ {
+ ep_ptr->internal_infos.endpoint_state = UCS_RM_EP_XRMPROCESSING;
+ TR_INFO((self->base_ptr->ucs_user_ptr, "[EPM]", "XRM has been ordered to create following Endpoint: %X", 1U, ep_ptr));
+ }
+ else
+ {
+ TR_INFO((self->base_ptr->ucs_user_ptr, "[EPM]", "Following Endpoint {%X} has already been built", 1U, ep_ptr));
+ }
+ }
+ else if (result == UCS_RET_ERR_ALREADY_SET)
+ {
+ if (ep_ptr->internal_infos.endpoint_state == UCS_RM_EP_IDLE)
+ {
+ ep_ptr->internal_infos.endpoint_state = UCS_RM_EP_BUILT;
+ TR_INFO((self->base_ptr->ucs_user_ptr, "[EPM]", "Following Endpoint {%X} has already been built", 1U, ep_ptr));
+ }
+ }
+ else if (result == UCS_RET_ERR_NOT_AVAILABLE)
+ {
+ /* Set the internal error */
+ ep_ptr->internal_infos.endpoint_state = UCS_RM_EP_IDLE;
+ ep_ptr->internal_infos.xrm_result.code = UCS_XRM_RES_ERR_BUILD;
+ ep_ptr->internal_infos.xrm_result.details.result_type = UCS_XRM_RESULT_TYPE_INT;
+ ep_ptr->internal_infos.xrm_result.details.int_result = result;
+ }
+ }
+
+ return result;
+}
+
+/*! \brief Processes the destruction of the given endpoint
+ * \param self Instance pointer
+ * \param ep_ptr reference to an endpoint
+ * \return Possible return values are
+ * - \c UCS_RET_ERR_API_LOCKED the API is locked. Endpoint is currently being processed.
+ * - \c UCS_RET_SUCCESS the build process was set successfully
+ * - \c UCS_RET_ERR_PARAM At least one parameter is not correct, either NULL pointer in the param list or reference_cnt of the endpoint is NULL.
+ * - \c UCS_RET_ERR_ALREADY_SET the endpoint has already been set
+ * - \c UCS_RET_ERR_NOT_AVAILABLE the endpoint cannot be destroyed since its reference_cnt is greater than 1, i.e. it's in use.
+ * - \c UCS_RET_ERR_INVALID_SHADOW the endpoint cannot be destroyed since its reference_cnt is greater than 1, i.e. it's in use.
+ */
+Ucs_Return_t Epm_SetDestroyProcess(CEndpointManagement * self, Ucs_Rm_EndPoint_t * ep_ptr)
+{
+ Ucs_Return_t result = UCS_RET_ERR_PARAM;
+ bool can_be_destroyed = true;
+
+ if ((self != NULL) && (ep_ptr != NULL) )
+ {
+ if (UCS_RM_EP_SOURCE == ep_ptr->endpoint_type)
+ {
+ if (ep_ptr->internal_infos.reference_cnt == 0U)
+ {
+ can_be_destroyed = false;
+ result = UCS_RET_ERR_PARAM;
+ }
+ else if (ep_ptr->internal_infos.reference_cnt > 1U)
+ {
+ ep_ptr->internal_infos.reference_cnt--;
+ can_be_destroyed = false;
+ result = UCS_RET_ERR_INVALID_SHADOW;
+ }
+ }
+
+ if (can_be_destroyed)
+ {
+ result = Xrm_Destroy(Fac_GetXrmByJobList(self->fac_ptr, ep_ptr->jobs_list_ptr), ep_ptr->jobs_list_ptr);
+ if (result == UCS_RET_SUCCESS)
+ {
+ ep_ptr->internal_infos.endpoint_state = UCS_RM_EP_XRMPROCESSING;
+ TR_INFO((self->base_ptr->ucs_user_ptr, "[EPM]", "XRM has been ordered to destroy following Endpoint {%X}", 1U, ep_ptr));
+ }
+ else if (result == UCS_RET_ERR_ALREADY_SET)
+ {
+ if (ep_ptr->internal_infos.endpoint_state == UCS_RM_EP_BUILT)
+ {
+ ep_ptr->internal_infos.endpoint_state = UCS_RM_EP_IDLE;
+ TR_INFO((self->base_ptr->ucs_user_ptr, "[EPM]", "Following Endpoint {%X} has already been destroyed", 1U, ep_ptr));
+ }
+ }
+ else if (result == UCS_RET_ERR_NOT_AVAILABLE)
+ {
+ if (ep_ptr->internal_infos.endpoint_state == UCS_RM_EP_BUILT)
+ {
+ ep_ptr->internal_infos.endpoint_state = UCS_RM_EP_IDLE;
+ TR_INFO((self->base_ptr->ucs_user_ptr, "[EPM]", "Following Endpoint {%X} has already been destroyed", 1U, ep_ptr));
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+/*! \brief Returns the state (idle, processing or built) of the given endpoint.
+ * \param self Instance pointer.
+ * \param ep_ptr Reference to the endpoint to be looked for
+ * \return state of the endpoint.
+ */
+Ucs_Rm_EndPointState_t Epm_GetState(CEndpointManagement * self, Ucs_Rm_EndPoint_t * ep_ptr)
+{
+ MISC_UNUSED (self);
+
+ return (ep_ptr != NULL) ? ep_ptr->internal_infos.endpoint_state:UCS_RM_EP_IDLE;
+}
+
+/*! \brief Forces EPM to reset the state of this endpoint.
+ * \param self Instance pointer.
+ * \param ep_ptr Reference to the endpoint to be looked for.
+ */
+void Epm_ResetState(CEndpointManagement * self, Ucs_Rm_EndPoint_t * ep_ptr)
+{
+ MISC_UNUSED (self);
+
+ if (ep_ptr != NULL)
+ {
+ ep_ptr->internal_infos.endpoint_state = UCS_RM_EP_IDLE;
+ ep_ptr->internal_infos.xrm_result.code = UCS_XRM_RES_UNKNOWN;
+ }
+}
+
+/*! \brief Sets the connection label of the given endpoint.
+ * \param self Instance pointer.
+ * \param ep_ptr Reference to the endpoint to be looked for
+ * \param conn_label connection label to be set
+ */
+void Epm_SetConnectionLabel(CEndpointManagement * self, Ucs_Rm_EndPoint_t * ep_ptr, uint16_t conn_label)
+{
+ MISC_UNUSED (self);
+
+ if (ep_ptr != NULL)
+ {
+ ep_ptr->internal_infos.connection_label = conn_label;
+ }
+}
+
+/*! \brief Returns the connection label of the given endpoint.
+ * \param self Instance pointer.
+ * \param ep_ptr Reference to the endpoint to be looked for
+ * \return connection label of the endpoint.
+ */
+uint16_t Epm_GetConnectionLabel(CEndpointManagement * self, Ucs_Rm_EndPoint_t * ep_ptr)
+{
+ MISC_UNUSED (self);
+
+ return (ep_ptr != NULL) ? ep_ptr->internal_infos.connection_label:0U;
+}
+
+/*! \brief This function must be called when a device get invalid.
+ * \param self Reference to the MNS instance.
+ * \param destination_address MOST device address of the target.
+ */
+void Epm_ReportInvalidDevice(CEndpointManagement *self, uint16_t destination_address)
+{
+ if (MSG_ADDR_INIC != destination_address)
+ {
+ CRemoteSyncManagement * rsm_inst = Fac_FindRsm(self->fac_ptr, destination_address);
+ if (NULL != rsm_inst)
+ {
+ Rsm_ReportSyncLost(rsm_inst);
+ }
+ }
+}
+
+/*! \brief Whenever this function has been called, the EndpointManager has to inform his sub-modules that a shutdown occurred.
+ * This function forwards the Network "NotAvailable" information
+ * \param self Instance pointer.
+ */
+void Epm_ReportShutDown(CEndpointManagement * self)
+{
+ Fac_Foreach(self->fac_ptr, FAC_INST_RSM, &Epm_RsmReportSyncLost, NULL);
+}
+
+/*! \brief Function signature used for monitoring the XRM resources.
+ * \param resource_type The XRM resource type to be looked for
+ * \param resource_ptr Reference to the resource to be looked for
+ * \param resource_infos Resource information
+ * \param endpoint_inst_ptr Reference to the endpoint object that encapsulates the given resource.
+ * \param user_ptr User reference provided in \ref Ucs_InitData_t "Ucs_InitData_t::user_ptr"
+ */
+void Epm_XrmResDebugCb (Ucs_Xrm_ResourceType_t resource_type, Ucs_Xrm_ResObject_t *resource_ptr,
+ Ucs_Xrm_ResourceInfos_t resource_infos, void *endpoint_inst_ptr, void *user_ptr)
+{
+ Ucs_Rm_EndPoint_t * ep_ptr = (Ucs_Rm_EndPoint_t *)endpoint_inst_ptr;
+ if (ep_ptr != NULL)
+ {
+ CEndpointManagement * self = (CEndpointManagement *)(void *)ep_ptr->internal_infos.epm_inst;
+ if (self->res_debugging_fptr != NULL)
+ {
+ self->res_debugging_fptr(resource_type, resource_ptr, resource_infos, ep_ptr, user_ptr);
+ }
+ }
+}
+
+/*------------------------------------------------------------------------------------------------*/
+/* Private Methods */
+/*------------------------------------------------------------------------------------------------*/
+/*! \brief Reports "SyncLost" to the RSM instance returned.
+ * \param inst_type The instance type to be looked for.
+ * \param inst_ptr Reference to the instance to be looked for.
+ * \param ud_ptr Reference to the user data.
+ * \return false in order to retrieve the next instance of the given type, otherwise false.
+ */
+static bool Epm_RsmReportSyncLost(Fac_Inst_t inst_type, void * inst_ptr, void *ud_ptr)
+{
+ bool ret_val = false;
+ MISC_UNUSED(ud_ptr);
+
+ switch (inst_type)
+ {
+ case FAC_INST_RSM:
+ Rsm_ReportSyncLost((CRemoteSyncManagement *)inst_ptr);
+ break;
+
+ default:
+ ret_val = true;
+ break;
+ }
+
+ return ret_val;
+}
+
+/*------------------------------------------------------------------------------------------------*/
+/* Callback Functions */
+/*------------------------------------------------------------------------------------------------*/
+/*! \brief XRM report callback function.
+ * \param node_address The node address from which the results come
+ * \param connection_label Returned MOST network connection label
+ * \param result Result of the job
+ * \param user_arg Reference to the user argument
+ */
+static void Epm_XrmReportCb(uint16_t node_address, uint16_t connection_label, Ucs_Xrm_Result_t result, void * user_arg)
+{
+ Ucs_Rm_EndPoint_t * ep_ptr = (Ucs_Rm_EndPoint_t *)user_arg;
+ uint8_t handle_not_found = 0x32U;
+ uint8_t error_id = 2U;
+
+ MISC_UNUSED (node_address);
+
+ if (ep_ptr != NULL)
+ {
+ ep_ptr->internal_infos.xrm_result = result;
+ switch (result.code)
+ {
+ case UCS_XRM_RES_SUCCESS_BUILD:
+ ep_ptr->internal_infos.connection_label = connection_label;
+ ep_ptr->internal_infos.endpoint_state = UCS_RM_EP_BUILT;
+ if (ep_ptr->endpoint_type == UCS_RM_EP_SOURCE)
+ {
+ ep_ptr->internal_infos.reference_cnt++;
+ }
+ TR_INFO((((CEndpointManagement *)(void *)ep_ptr->internal_infos.epm_inst)->base_ptr->ucs_user_ptr, "[EPM]", "Following Endpoint {%X} has been successfully built", 1U, ep_ptr));
+ break;
+
+ case UCS_XRM_RES_SUCCESS_DESTROY:
+ ep_ptr->internal_infos.connection_label = 0xFFFFU;
+ ep_ptr->internal_infos.endpoint_state = UCS_RM_EP_IDLE;
+ if (ep_ptr->endpoint_type == UCS_RM_EP_SOURCE)
+ {
+ if (ep_ptr->internal_infos.reference_cnt > 0U)
+ {
+ ep_ptr->internal_infos.reference_cnt--;
+ }
+ }
+ TR_INFO((((CEndpointManagement *)(void *)ep_ptr->internal_infos.epm_inst)->base_ptr->ucs_user_ptr, "[EPM]", "Following Endpoint {%X} has been successfully destroyed", 1U, ep_ptr));
+ break;
+
+ case UCS_XRM_RES_RC_AUTO_DESTROYED:
+ TR_ERROR((((CEndpointManagement *)(void *)ep_ptr->internal_infos.epm_inst)->base_ptr->ucs_user_ptr, "[EPM]", "Following Endpoint {%X} has been auto destroyed.", 1U, ep_ptr));
+ ep_ptr->internal_infos.connection_label = 0xFFFFU;
+ ep_ptr->internal_infos.endpoint_state = UCS_RM_EP_IDLE;
+ if (ep_ptr->endpoint_type == UCS_RM_EP_SOURCE)
+ {
+ ep_ptr->internal_infos.reference_cnt = 0U;
+ }
+ if(Sub_GetNumObservers(&ep_ptr->internal_infos.subject_obj) > 0U)
+ {
+ Sub_Notify(&ep_ptr->internal_infos.subject_obj, (void *)ep_ptr);
+ }
+ break;
+
+ case UCS_XRM_RES_ERR_CONFIG:
+ case UCS_XRM_RES_ERR_SYNC:
+ case UCS_XRM_RES_ERR_BUILD:
+ ep_ptr->internal_infos.connection_label = 0xFFFFU;
+ ep_ptr->internal_infos.endpoint_state = UCS_RM_EP_IDLE;
+ TR_ERROR((((CEndpointManagement *)(void *)ep_ptr->internal_infos.epm_inst)->base_ptr->ucs_user_ptr, "[EPM]", "Building endpoint {%X} failed. Error_Code: 0x%02X", 2U, ep_ptr, result.code));
+ break;
+
+ case UCS_XRM_RES_ERR_DESTROY:
+ ep_ptr->internal_infos.endpoint_state = UCS_RM_EP_IDLE;
+ if (ep_ptr->internal_infos.xrm_result.details.result_type == UCS_XRM_RESULT_TYPE_TGT)
+ {
+ if ((ep_ptr->internal_infos.xrm_result.details.inic_result.code == UCS_RES_ERR_CONFIGURATION) &&
+ (ep_ptr->internal_infos.xrm_result.details.inic_result.info_ptr != NULL) &&
+ (ep_ptr->internal_infos.xrm_result.details.inic_result.info_size > 2U))
+ {
+ if (ep_ptr->internal_infos.xrm_result.details.inic_result.info_ptr[error_id] == handle_not_found)
+ {
+ ep_ptr->internal_infos.xrm_result.code = UCS_XRM_RES_SUCCESS_DESTROY;
+ }
+ }
+ }
+ if (ep_ptr->endpoint_type == UCS_RM_EP_SOURCE)
+ {
+ ep_ptr->internal_infos.reference_cnt = 0U;
+ }
+ TR_ERROR((((CEndpointManagement *)(void *)ep_ptr->internal_infos.epm_inst)->base_ptr->ucs_user_ptr, "[EPM]", "Destroying endpoint {%X} failed. Error_Code: 0x%02X", 2U, ep_ptr, result.code));
+ break;
+
+ case UCS_XRM_RES_ERR_INV_LIST:
+ TR_ERROR((((CEndpointManagement *)(void *)ep_ptr->internal_infos.epm_inst)->base_ptr->ucs_user_ptr, "[EPM]", "Request of invalid lists on endpoint {%X} failed.", 1U, ep_ptr));
+ if (ep_ptr->internal_infos.endpoint_state == UCS_RM_EP_BUILT)
+ {
+ ep_ptr->internal_infos.connection_label = 0xFFFFU;
+ ep_ptr->internal_infos.endpoint_state = UCS_RM_EP_IDLE;
+ if(Sub_GetNumObservers(&ep_ptr->internal_infos.subject_obj) > 0U)
+ {
+ Sub_Notify(&ep_ptr->internal_infos.subject_obj, (void *)ep_ptr);
+ }
+ }
+ break;
+
+ default:
+ TR_ERROR((((CEndpointManagement *)(void *)ep_ptr->internal_infos.epm_inst)->base_ptr->ucs_user_ptr, "[EPM]", "Processing endpoint {%X} failed. Unknown Error_Code: 0x%02X", 2U, ep_ptr, result.code));
+ break;
+ }
+ }
+}
+
+/*!
+ * @}
+ * \endcond
+ */
+
+/*------------------------------------------------------------------------------------------------*/
+/* End of file */
+/*------------------------------------------------------------------------------------------------*/
+