CASA/CASA-auth-token/client/core/gettokenmsg.c
Juan Carlos Luciani 2c8668479c Splitted the non-java project into client and server projects in order
to be able to deliver the client component onto distributions targeting
desktops without having to deliver the server components. This commit is
for the resulting client project.
2006-11-13 05:20:43 +00:00

794 lines
26 KiB
C

/***********************************************************************
*
* Copyright (C) 2006 Novell, Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; version 2.1
* of the License.
*
* This library 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
* Library Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, Novell, Inc.
*
* To contact Novell about this file by physical or electronic mail,
* you may find current contact information at www.novell.com.
*
* Author: Juan Carlos Luciani <jluciani@novell.com>
*
***********************************************************************/
//===[ Include files ]=====================================================
#include "internal.h"
//===[ Type definitions ]==================================================
//
// Parse states
//
#define AWAITING_ROOT_ELEMENT_START 0x0
#define AWAITING_ROOT_ELEMENT_END 0x1
#define AWAITING_STATUS_ELEMENT_START 0x2
#define AWAITING_STATUS_ELEMENT_END 0x3
#define AWAITING_STATUS_DATA 0x4
#define AWAITING_DESCRIPTION_ELEMENT_START 0x5
#define AWAITING_DESCRIPTION_ELEMENT_END 0x6
#define AWAITING_DESCRIPTION_DATA 0x7
#define AWAITING_LIFETIME_DATA 0x8
#define AWAITING_LIFETIME_ELEMENT_START 0x9
#define AWAITING_LIFETIME_ELEMENT_END 0xA
#define AWAITING_AUTH_TOKEN_ELEMENT_START 0xB
#define AWAITING_AUTH_TOKEN_ELEMENT_END 0xC
#define AWAITING_AUTH_TOKEN_DATA 0xD
#define DONE_PARSING 0xE
//
// Get Authentication Token Response Parse Structure
//
typedef struct _GetAuthTokenRespParse
{
XML_Parser p;
int state;
int elementDataProcessed;
GetAuthTokenResp *pGetAuthTokenResp;
CasaStatus status;
} GetAuthTokenRespParse, *PGetAuthTokenRespParse;
//===[ Function prototypes ]===============================================
//===[ Global variables ]==================================================
//++=======================================================================
char*
BuildGetAuthTokenMsg(
IN const char *pServiceName,
IN const char *pHostName,
IN char *pSessionToken)
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes:
//
// L2
//=======================================================================--
{
char *pMsg = NULL;
int bufferSize;
DbgTrace(1, "-BuildGetAuthTokenMsg- Start\n", 0);
/*
* The format of the get authentication token request message
* is as follows:
*
* <?xml version="1.0" encoding="ISO-8859-1"?>
* <get_auth_token_req>
* <service>service name</service>
* <host>host name</host>
* <session_token>session token data</session_token>
* </get_auth_token_req>
*
*/
// Determine the buffer size necessary to hold the msg
bufferSize = strlen(XML_DECLARATION)
+ 2 // crlf
+ 1 // <
+ strlen(GET_AUTH_TOKEN_REQUEST_ELEMENT_NAME)
+ 3 // >crlf
+ 1 // <
+ strlen(SERVICE_ELEMENT_NAME)
+ 1 // >
+ strlen(pServiceName)
+ 2 // </
+ strlen(SERVICE_ELEMENT_NAME)
+ 3 // >crlf
+ 1 // <
+ strlen(HOST_ELEMENT_NAME)
+ 1 // >
+ strlen(pHostName)
+ 2 // </
+ strlen(HOST_ELEMENT_NAME)
+ 3 // >crlf
+ 1 // <
+ strlen(SESSION_TOKEN_ELEMENT_NAME)
+ 1 // >
+ strlen(pSessionToken)
+ 2 // </
+ strlen(SESSION_TOKEN_ELEMENT_NAME)
+ 3 // >crlf
+ 2 // </
+ strlen(GET_AUTH_TOKEN_REQUEST_ELEMENT_NAME)
+ 2; // >null
// Allocate the msg buffer
pMsg = (char*) malloc(bufferSize);
if (pMsg)
{
// Now build the message
memset(pMsg, 0, bufferSize);
strcat(pMsg, XML_DECLARATION);
strcat(pMsg, "\r\n");
strcat(pMsg, "<");
strcat(pMsg, GET_AUTH_TOKEN_REQUEST_ELEMENT_NAME);
strcat(pMsg, ">\r\n");
strcat(pMsg, "<");
strcat(pMsg, SERVICE_ELEMENT_NAME);
strcat(pMsg, ">");
strcat(pMsg, pServiceName);
strcat(pMsg, "</");
strcat(pMsg, SERVICE_ELEMENT_NAME);
strcat(pMsg, ">\r\n");
strcat(pMsg, "<");
strcat(pMsg, HOST_ELEMENT_NAME);
strcat(pMsg, ">");
strcat(pMsg, pHostName);
strcat(pMsg, "</");
strcat(pMsg, HOST_ELEMENT_NAME);
strcat(pMsg, ">\r\n");
strcat(pMsg, "<");
strcat(pMsg, SESSION_TOKEN_ELEMENT_NAME);
strcat(pMsg, ">");
strcat(pMsg, pSessionToken);
strcat(pMsg, "</");
strcat(pMsg, SESSION_TOKEN_ELEMENT_NAME);
strcat(pMsg, ">\r\n");
strcat(pMsg, "</");
strcat(pMsg, GET_AUTH_TOKEN_REQUEST_ELEMENT_NAME);
strcat(pMsg, ">");
}
else
{
DbgTrace(0, "-BuildGetAuthTokenMsg- Buffer allocation error\n", 0);
}
DbgTrace(1, "-BuildGetAuthTokenMsg- End, pMsg = %0lX\n", (long) pMsg);
return pMsg;
}
//++=======================================================================
static
void XMLCALL
GetAuthTokenRespStartElementHandler(
IN GetAuthTokenRespParse *pGetAuthTokenRespParse,
IN const XML_Char *name,
IN const XML_Char **atts)
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes:
//
// L2
//=======================================================================--
{
DbgTrace(2, "-GetAuthTokenRespStartElementHandler- Start\n", 0);
// Proceed based on the state
switch (pGetAuthTokenRespParse->state)
{
case AWAITING_ROOT_ELEMENT_START:
// In this state, we are only expecting the Get Authentication
// Token Response Element.
if (strcmp(name, GET_AUTH_TOKEN_RESPONSE_ELEMENT_NAME) == 0)
{
// Good, advance to the next state.
pGetAuthTokenRespParse->state = AWAITING_STATUS_ELEMENT_START;
}
else
{
DbgTrace(0, "-GetAuthTokenRespStartElementHandler- Un-expected start element\n", 0);
XML_StopParser(pGetAuthTokenRespParse->p, XML_FALSE);
}
break;
case AWAITING_STATUS_ELEMENT_START:
// In this state, we are only expecting the Status Element.
if (strcmp(name, STATUS_ELEMENT_NAME) == 0)
{
// Good, advance to the next state.
pGetAuthTokenRespParse->state = AWAITING_DESCRIPTION_ELEMENT_START;
}
else
{
DbgTrace(0, "-GetAuthTokenRespStartElementHandler- Un-expected start element\n", 0);
XML_StopParser(pGetAuthTokenRespParse->p, XML_FALSE);
}
break;
case AWAITING_DESCRIPTION_ELEMENT_START:
// In this state, we are only expecting the Description Element.
if (strcmp(name, DESCRIPTION_ELEMENT_NAME) == 0)
{
// Good, advance to the next state.
pGetAuthTokenRespParse->state = AWAITING_DESCRIPTION_DATA;
}
else
{
DbgTrace(0, "-GetAuthTokenRespStartElementHandler- Un-expected start element\n", 0);
XML_StopParser(pGetAuthTokenRespParse->p, XML_FALSE);
}
break;
case AWAITING_AUTH_TOKEN_ELEMENT_START:
// In this state, we are only expecting the Authentication Token Element.
if (strcmp(name, AUTH_TOKEN_ELEMENT_NAME) == 0)
{
// Good, advance to the next state.
pGetAuthTokenRespParse->state = AWAITING_LIFETIME_ELEMENT_START;
}
else
{
DbgTrace(0, "-GetAuthTokenRespStartElementHandler- Un-expected start element\n", 0);
XML_StopParser(pGetAuthTokenRespParse->p, XML_FALSE);
}
break;
case AWAITING_LIFETIME_ELEMENT_START:
// In this state, we are only expecting the Lifetime Element.
if (strcmp(name, LIFETIME_ELEMENT_NAME) == 0)
{
// Good, advance to the next state.
pGetAuthTokenRespParse->state = AWAITING_LIFETIME_DATA;
}
else
{
DbgTrace(0, "-GetAuthTokenRespStartElementHandler- Un-expected start element\n", 0);
XML_StopParser(pGetAuthTokenRespParse->p, XML_FALSE);
}
break;
default:
DbgTrace(0, "-GetAuthTokenRespStartElementHandler- Un-expected state = %d\n", pGetAuthTokenRespParse->state);
XML_StopParser(pGetAuthTokenRespParse->p, XML_FALSE);
break;
}
DbgTrace(2, "-GetAuthTokenRespStartElementHandler- End\n", 0);
}
//++=======================================================================
static
CasaStatus
ConsumeElementData(
IN GetAuthTokenRespParse *pGetAuthTokenRespParse,
IN const XML_Char *s,
IN int len,
INOUT char **ppElementData,
INOUT int *pElementDataLen)
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes:
//
// L2
//=======================================================================--
{
CasaStatus retStatus = CASA_STATUS_SUCCESS;
DbgTrace(3, "-ConsumeElementData- Start\n", 0);
// Proceed based on whether or not we have already consumed data
// for this element.
if (*ppElementData == NULL)
{
// We have not yet consumed data for this element
pGetAuthTokenRespParse->elementDataProcessed = len;
// Allocate a buffer to hold this element data (null terminated).
*ppElementData = (char*) malloc(len + 1);
if (*ppElementData)
{
memset(*ppElementData, 0, len + 1);
memcpy(*ppElementData, s, len);
// Return the length of the element data buffer
*pElementDataLen = pGetAuthTokenRespParse->elementDataProcessed + 1;
}
else
{
DbgTrace(0, "-ConsumeElementData- Buffer allocation failure\n", 0);
retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
CASA_FACILITY_AUTHTOKEN,
CASA_STATUS_INSUFFICIENT_RESOURCES);
}
}
else
{
char *pNewBuf;
// We have already received token data, append this data to it.
pNewBuf = (char*) malloc(pGetAuthTokenRespParse->elementDataProcessed + len + 1);
if (pNewBuf)
{
memset(pNewBuf,
0,
pGetAuthTokenRespParse->elementDataProcessed + len + 1);
memcpy(pNewBuf,
*ppElementData,
pGetAuthTokenRespParse->elementDataProcessed);
memcpy(pNewBuf + pGetAuthTokenRespParse->elementDataProcessed, s, len);
pGetAuthTokenRespParse->elementDataProcessed += len;
// Swap the buffers
free(*ppElementData);
*ppElementData = pNewBuf;
// Return the length of the element data buffer
*pElementDataLen = pGetAuthTokenRespParse->elementDataProcessed + 1;
}
else
{
DbgTrace(0, "-ConsumeElementData- Buffer allocation failure\n", 0);
retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
CASA_FACILITY_AUTHTOKEN,
CASA_STATUS_INSUFFICIENT_RESOURCES);
}
}
DbgTrace(3, "-ConsumeElementData- End, retStatus = %08X\n", retStatus);
return retStatus;
}
//++=======================================================================
static
void XMLCALL
GetAuthTokenRespCharDataHandler(
IN GetAuthTokenRespParse *pGetAuthTokenRespParse,
IN const XML_Char *s,
IN int len)
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes:
//
// L2
//=======================================================================--
{
DbgTrace(2, "-GetAuthTokenRespCharDataHandler- Start\n", 0);
// Just exit if being called to process white space
if (*s == '\n' || *s == '\r' || *s == '\t' || *s == ' ')
{
goto exit;
}
// Proceed based on the state
switch (pGetAuthTokenRespParse->state)
{
case AWAITING_DESCRIPTION_DATA:
case AWAITING_DESCRIPTION_ELEMENT_END:
// Ignore the status description data for now.
// tbd
// Advanced to the next state
pGetAuthTokenRespParse->state = AWAITING_DESCRIPTION_ELEMENT_END;
break;
case AWAITING_STATUS_DATA:
// Set the appropriate status in the AuthenticationResp based on the
// returned status.
if (strncmp(HTTP_OK_STATUS_CODE, s, len) == 0)
{
pGetAuthTokenRespParse->status = CASA_STATUS_SUCCESS;
}
else if (strncmp(HTTP_UNAUTHORIZED_STATUS_CODE, s, len) == 0)
{
pGetAuthTokenRespParse->status = CasaStatusBuild(CASA_SEVERITY_ERROR,
CASA_FACILITY_AUTHTOKEN,
CASA_STATUS_AUTHENTICATION_FAILURE);
}
else if (strncmp(HTTP_SERVER_ERROR_STATUS_CODE, s, len) == 0)
{
pGetAuthTokenRespParse->status = CasaStatusBuild(CASA_SEVERITY_ERROR,
CASA_FACILITY_AUTHTOKEN,
CASA_STATUS_SERVER_ERROR);
}
else
{
DbgTrace(0, "-GetAuthTokenRespCharDataHandler- Un-expected status\n", 0);
pGetAuthTokenRespParse->status = CasaStatusBuild(CASA_SEVERITY_ERROR,
CASA_FACILITY_AUTHTOKEN,
CASA_STATUS_UNSUCCESSFUL);
}
// Advanced to the next state
pGetAuthTokenRespParse->state = AWAITING_STATUS_ELEMENT_END;
break;
case AWAITING_LIFETIME_DATA:
// Convert the lifetime string to a numeric value
pGetAuthTokenRespParse->pGetAuthTokenResp->tokenLifetime = dtoul(s, len);
// Advanced to the next state
pGetAuthTokenRespParse->state = AWAITING_LIFETIME_ELEMENT_END;
break;
case AWAITING_AUTH_TOKEN_DATA:
case AWAITING_AUTH_TOKEN_ELEMENT_END:
// Consume the data
pGetAuthTokenRespParse->status = ConsumeElementData(pGetAuthTokenRespParse,
s,
len,
&pGetAuthTokenRespParse->pGetAuthTokenResp->pToken,
&pGetAuthTokenRespParse->pGetAuthTokenResp->tokenLen);
if (CASA_SUCCESS(pGetAuthTokenRespParse->status))
{
// Advanced to the next state
pGetAuthTokenRespParse->state = AWAITING_AUTH_TOKEN_ELEMENT_END;
}
else
{
XML_StopParser(pGetAuthTokenRespParse->p, XML_FALSE);
}
break;
default:
DbgTrace(0, "-GetAuthTokenRespCharDataHandler- Un-expected state = %d\n", pGetAuthTokenRespParse->state);
XML_StopParser(pGetAuthTokenRespParse->p, XML_FALSE);
break;
}
exit:
DbgTrace(2, "-GetAuthTokenRespCharDataHandler- End\n", 0);
}
//++=======================================================================
static
void XMLCALL
GetAuthTokenRespEndElementHandler(
IN GetAuthTokenRespParse *pGetAuthTokenRespParse,
IN const XML_Char *name)
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes:
//
// L2
//=======================================================================--
{
DbgTrace(2, "-GetAuthTokenRespEndElementHandler- Start\n", 0);
// Proceed based on the state
switch (pGetAuthTokenRespParse->state)
{
case AWAITING_ROOT_ELEMENT_END:
// In this state, we are only expecting the Get Authentication
// Token Response Element.
if (strcmp(name, GET_AUTH_TOKEN_RESPONSE_ELEMENT_NAME) == 0)
{
// Done.
pGetAuthTokenRespParse->state = DONE_PARSING;
}
else
{
DbgTrace(0, "-GetAuthTokenRespEndHandler- Un-expected end element\n", 0);
XML_StopParser(pGetAuthTokenRespParse->p, XML_FALSE);
}
break;
case AWAITING_DESCRIPTION_ELEMENT_END:
// In this state, we are only expecting the Description Element.
if (strcmp(name, DESCRIPTION_ELEMENT_NAME) == 0)
{
// Good, advance to the next state.
pGetAuthTokenRespParse->state = AWAITING_STATUS_DATA;
}
else
{
DbgTrace(0, "-GetAuthTokenRespEndElementHandler- Un-expected end element\n", 0);
XML_StopParser(pGetAuthTokenRespParse->p, XML_FALSE);
}
break;
case AWAITING_STATUS_ELEMENT_END:
// In this state, we are only expecting the Status Element.
if (strcmp(name, STATUS_ELEMENT_NAME) == 0)
{
// Good, advance to the next state based on the status code.
if (CASA_SUCCESS(pGetAuthTokenRespParse->status))
{
// The request completed successfully
pGetAuthTokenRespParse->state = AWAITING_AUTH_TOKEN_ELEMENT_START;
}
else
{
pGetAuthTokenRespParse->state = AWAITING_ROOT_ELEMENT_END;
}
}
else
{
DbgTrace(0, "-GetAuthTokenRespEndElementHandler- Un-expected start element\n", 0);
XML_StopParser(pGetAuthTokenRespParse->p, XML_FALSE);
}
break;
case AWAITING_LIFETIME_ELEMENT_END:
// In this state, we are only expecting the Lifetime Element.
if (strcmp(name, LIFETIME_ELEMENT_NAME) == 0)
{
// Good, advance to the next state.
pGetAuthTokenRespParse->state = AWAITING_AUTH_TOKEN_DATA;
}
else
{
DbgTrace(0, "-GetAuthTokenRespEndElementHandler- Un-expected start element\n", 0);
XML_StopParser(pGetAuthTokenRespParse->p, XML_FALSE);
}
break;
case AWAITING_AUTH_TOKEN_ELEMENT_END:
// In this state, we are only expecting the Authentication Token Element.
if (strcmp(name, AUTH_TOKEN_ELEMENT_NAME) == 0)
{
// Good, advance to the next state.
pGetAuthTokenRespParse->state = AWAITING_ROOT_ELEMENT_END;
}
else
{
DbgTrace(0, "-GetAuthTokenRespEndElementHandler- Un-expected start element\n", 0);
XML_StopParser(pGetAuthTokenRespParse->p, XML_FALSE);
}
break;
default:
DbgTrace(0, "-GetAuthTokenRespEndElementHandler- Un-expected state = %d\n", pGetAuthTokenRespParse->state);
XML_StopParser(pGetAuthTokenRespParse->p, XML_FALSE);
break;
}
DbgTrace(2, "-GetAuthTokenRespEndElementHandler- End\n", 0);
}
//++=======================================================================
CasaStatus
CreateGetAuthTokenResp(
IN char *pRespMsg,
IN int respLen,
INOUT GetAuthTokenResp **ppGetAuthTokenResp)
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes:
//
// L2
//=======================================================================--
{
CasaStatus retStatus = CASA_STATUS_SUCCESS;
GetAuthTokenRespParse getAuthTokenRespParse = {0};
GetAuthTokenResp *pGetAuthTokenResp;
DbgTrace(1, "-CreateGetAuthTokenResp- Start\n", 0);
/*
* When a get authentication token request is processed successfully, the
* server replies to the client with a message with the following format:
*
* <?xml version="1.0" encoding="ISO-8859-1"?>
* <get_auth_token_resp>
* <status><description>ok</description>200</status>
* <auth_token><lifetime>lifetime value</lifetime>session token data</auth_token>
* </get_auth_token_resp>
*
* When a get authentication token request fails to be successfully processed,
* the server responds with an error and an error description string. The message
* format of an unsuccessful reply is as follows:
*
* <?xml version="1.0" encoding="ISO-8859-1"?>
* <get_auth_token_resp>
* <status><description>status description</description>status code</status>
* </get_auth_token_resp>
*
* Plase note that the protocol utilizes the status codes defined
* in the HTTP 1.1 Specification.
*
*/
// Allocate GetAuthTokenResp object
pGetAuthTokenResp = malloc(sizeof(*pGetAuthTokenResp));
if (pGetAuthTokenResp)
{
XML_Parser p;
// Initialize the GetAuthTokenResp object and set it in the
// parse oject.
memset(pGetAuthTokenResp, 0, sizeof(*pGetAuthTokenResp));
getAuthTokenRespParse.pGetAuthTokenResp = pGetAuthTokenResp;
// Create parser
p = XML_ParserCreate(NULL);
if (p)
{
// Keep track of the parser in our parse object
getAuthTokenRespParse.p = p;
// Initialize the status within the parse object
getAuthTokenRespParse.status = CASA_STATUS_SUCCESS;
// Set the start and end element handlers
XML_SetElementHandler(p,
(XML_StartElementHandler) GetAuthTokenRespStartElementHandler,
(XML_EndElementHandler) GetAuthTokenRespEndElementHandler);
// Set the character data handler
XML_SetCharacterDataHandler(p, (XML_CharacterDataHandler) GetAuthTokenRespCharDataHandler);
// Set our user data
XML_SetUserData(p, &getAuthTokenRespParse);
// Parse the document
if (XML_Parse(p, pRespMsg, respLen, 1) == XML_STATUS_OK)
{
// Verify that the parse operation completed successfully
if (getAuthTokenRespParse.state == DONE_PARSING)
{
// The parse operation succeded, obtain the status returned
// by the server.
retStatus = getAuthTokenRespParse.status;
}
else
{
DbgTrace(0, "-CreateGetAuthTokenResp- Parse operation did not complete\n", 0);
// Check if a status has been recorded
if (getAuthTokenRespParse.status != CASA_STATUS_SUCCESS)
{
retStatus = getAuthTokenRespParse.status;
}
else
{
retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
CASA_FACILITY_AUTHTOKEN,
CASA_STATUS_PROTOCOL_ERROR);
}
}
}
else
{
DbgTrace(0, "-CreateGetAuthTokenResp- Parse error %d\n", XML_GetErrorCode(p));
retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
CASA_FACILITY_AUTHTOKEN,
CASA_STATUS_PROTOCOL_ERROR);
}
// Free the parser
XML_ParserFree(p);
}
else
{
DbgTrace(0, "-CreateGetAuthTokenResp- Parser creation error\n", 0);
retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
CASA_FACILITY_AUTHTOKEN,
CASA_STATUS_INSUFFICIENT_RESOURCES);
}
// Return the AuthenticationResp object to the caller if necessary
if (CASA_SUCCESS(retStatus))
{
*ppGetAuthTokenResp = pGetAuthTokenResp;
}
else
{
free(pGetAuthTokenResp);
}
}
else
{
DbgTrace(0, "-CreateGetAuthTokenResp- Memory allocation error\n", 0);
retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
CASA_FACILITY_AUTHTOKEN,
CASA_STATUS_INSUFFICIENT_RESOURCES);
}
DbgTrace(1, "-CreateGetAuthTokenResp- End, retStatus = %08X\n", retStatus);
return retStatus;
}
//++=======================================================================
void
RelGetAuthTokenResp(
IN GetAuthTokenResp *pGetAuthTokenResp)
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes:
//
// L2
//=======================================================================--
{
DbgTrace(1, "-RelGetAuthTokenResp- Start\n", 0);
// Free the resources associated with the object
if (pGetAuthTokenResp->pToken)
free(pGetAuthTokenResp->pToken);
free(pGetAuthTokenResp);
DbgTrace(1, "-RelGetAuthTokenResp- End\n", 0);
}
//++=======================================================================
//++=======================================================================
//++=======================================================================