/* ****************************************************************************** * INTERPEAK SOURCE FILE * * Document no: @(#) $Name: VXWORKS_ITER40_2019102207 $ $RCSfile: ipcavium_ipsec.c,v $ $Revision: 1.11 $ * $Source: /home/interpeak/CVSRoot/ipcavium/src/ipcavium_ipsec.c,v $ * $Author: markus $ * $State: Exp $ $Locker: $ * * INTERPEAK_COPYRIGHT_STRING * Design and implementation by Markus Carlstedt ****************************************************************************** */ /* **************************************************************************** * 1 DESCRIPTION **************************************************************************** */ /* **************************************************************************** * 2 CONFIGURATION **************************************************************************** */ /* #include "ipcavium_config.h" */ /* **************************************************************************** * 3 INCLUDE FILES **************************************************************************** */ #define IPCOM_USE_CLIB_PROTO #include #include #include #include #include #include #include #ifdef IPIPSEC2 #include #endif /* #include "ipcavium.h" */ /* #include "ipcavium_h.h" */ #ifdef IP_PORT_LKM extern unsigned long octeon_crypto_enable(unsigned long *state); extern void octeon_crypto_disable(unsigned long *state, unsigned long crypto_flags); #endif /* **************************************************************************** * 4 DEFINES **************************************************************************** */ /* Syslog facility */ #ifdef IPCOM_SYSLOG_FACILITY #undef IPCOM_SYSLOG_FACILITY #endif #define IPCOM_SYSLOG_FACILITY IPCOM_LOG_IPCAVIUM /* Syslog priority */ #ifdef IPCOM_SYSLOG_PRIORITY #undef IPCOM_SYSLOG_PRIORITY #endif #ifdef IP_DEBUG #define IPCOM_SYSLOG_PRIORITY IPCOM_LOG_DEBUG #else #define IPCOM_SYSLOG_PRIORITY IPCOM_LOG_WARNING #endif /* **************************************************************************** * 5 TYPES **************************************************************************** */ /* **************************************************************************** * 6 EXTERN PROTOTYPES **************************************************************************** */ /* *=========================================================================== * OCTEON_OPENSSL *=========================================================================== * External prototypes for linkage with Cavium Networks crypto library * */ IP_EXTERN int DES_ede3_cbc_md5_decrypt(Ip_u8 *des_key, Ip_u16 auth_keylen, Ip_u8 *auth_key, Ip_u8 *des_iv, Ip_u8 *pktptr, Ip_u16 pktlen, Ip_u8 *outptr, Ip_u16 *outlen, Ip_u8 compdigest); IP_EXTERN int DES_ede3_cbc_md5_encrypt(Ip_u8 *des_key, Ip_u16 auth_keylen, Ip_u8 *auth_key, Ip_u8 *espheader, Ip_u8 *des_iv, Ip_u8 *pktptr, Ip_u16 pktlen, Ip_u8 *outptr, Ip_u16 *outlen); IP_EXTERN int DES_ede3_cbc_sha1_decrypt(Ip_u8 *des_key, Ip_u16 sha1_keylen, Ip_u8 *sha1_key, Ip_u8 *des_iv, Ip_u8 *pktptr, Ip_u16 pktlen, Ip_u8 *outptr, Ip_u16 *outlen, Ip_u8 compdigest); IP_EXTERN int DES_ede3_cbc_sha1_encrypt(Ip_u8 *des_key, Ip_u16 sha1_keylen, Ip_u8 *sha1_key, Ip_u8 *espheader, Ip_u8 *des_iv, Ip_u8 *pktptr, Ip_u16 pktlen, Ip_u8 *outptr, Ip_u16 *outlen); IP_EXTERN int AES_cbc_md5_decrypt(Ip_u16 aes_key_len, Ip_u8 *aes_key, Ip_u16 auth_keylen, Ip_u8 *auth_key, Ip_u8 *aes_iv, Ip_u8 *pktptr, Ip_u16 pktlen, Ip_u8 *outptr, Ip_u16 *outlen, Ip_u8 compdigest); IP_EXTERN int AES_cbc_md5_encrypt(Ip_u16 aes_key_len, Ip_u8 *aes_key, Ip_u16 auth_keylen, Ip_u8 *auth_key, Ip_u8 *espheader, Ip_u8 *aes_iv, Ip_u8 *pktptr, Ip_u16 pktlen, Ip_u8 *outptr, Ip_u16 *outlen); IP_EXTERN int AES_cbc_sha1_decrypt(Ip_u16 aes_key_len, Ip_u8 *aes_key, Ip_u16 sha1_keylen, Ip_u8 *sha1_key, Ip_u8 *aes_iv, Ip_u8 *pktptr, Ip_u16 pktlen, Ip_u8 *outptr, Ip_u16 *outlen, Ip_u8 compdigest); IP_EXTERN int AES_cbc_sha1_encrypt(Ip_u16 aes_key_len, Ip_u8 *aes_key, Ip_u16 sha1_keylen, Ip_u8 *sha1_key, Ip_u8 *espheader, Ip_u8 *aes_iv, Ip_u8 *pktptr, Ip_u16 pktlen, Ip_u8 *outptr, Ip_u16 *outlen); IP_EXTERN int AES_cbc_sha1_encrypt_fast(Ip_u16 aes_key_len, Ip_u8 *aes_key, Ip_u8 *espheader, Ip_u8 *aes_iv, Ip_u8 *pktptr, Ip_u16 pktlen, Ip_u8 *outptr, Ip_u8 *ictx, Ip_u8 *octx); IP_EXTERN int AES_cbc_sha1_decrypt_fast(Ip_u16 aes_key_len, Ip_u8 *aes_key, Ip_u8 *aes_iv, Ip_u8 *pktptr, Ip_u16 pktlen, Ip_u8 *outptr, Ip_u8 *ictx, Ip_u8 *octx); /* **************************************************************************** * 7 LOCAL PROTOTYPES **************************************************************************** */ IP_PUBLIC Ip_err ipcavium_create(void); IP_PUBLIC Ip_err ipcavium_configure(void); IP_PUBLIC Ip_err ipcavium_start(void); IP_PUBLIC const char *ipcavium_version(void); IP_PUBLIC int ipcavium_cmd_cavium(int argc, char **argv); /* **************************************************************************** * 8 DATA **************************************************************************** */ IP_GLOBAL int ipcavium_num_esp_3des_md5 = 0; IP_GLOBAL int ipcavium_num_esp_3des_sha1 = 0; IP_GLOBAL int ipcavium_num_esp_aes_md5 = 0; IP_GLOBAL int ipcavium_num_esp_aes_sha1 = 0; /* **************************************************************************** * 9 STATIC FUNCTIONS **************************************************************************** */ #ifdef IPIPSEC2 /* *=========================================================================== * ipcavium_ipsec_esp_crypto *=========================================================================== * Description: IPSEC ESP processing * Parameters: data - pointer to the packet data. * length - length of the packet. * sa - pointer to the sa (security association). * digest - pointer the message autentication code. * alg - algorithm * enc - encrypt(1) or decrypt(0) * Returns: 1 = ok * -1 = error */ IP_STATIC int ipcavium_ipsec_esp_crypto(Ip_u8 *data, Ip_u32 length, Iphwcrypto_esp_cbc_sa *sa, Ip_u8 *digest, int alg, int enc) { #ifdef IP_PORT_LKM unsigned long octeon_cop2_state[48]; unsigned long crypto_flags; #endif Ip_u16 outlen; int ret; Ip_u8 hmac[12]; Ip_u8 iv[IPIPSEC_MAX_IV_SIZE]; /* Because the IPSec daemons set hard affinity to a reserved CPU we do not risk preemption */ #ifdef IP_PORT_LKM crypto_flags = octeon_crypto_enable(octeon_cop2_state); #endif if (!enc) { /* Save original MAC. */ ipcom_memcpy(hmac, data + length - sa->authlen, sizeof(hmac)); } else { /* Must copy IV. IV update done in IPHWCRYPTO */ ipcom_memcpy(iv, sa->iv, sa->ivlen); } switch (alg) { case IPHWCRYPTO_ENGINE_ESP_3DES_CBC_MD5: if (enc) ret = DES_ede3_cbc_md5_encrypt(sa->ekey, sa->akeylen, sa->akey, data, iv, data + sa->hdrlen, length - sa->hdrlen, data, &outlen); else ret = DES_ede3_cbc_md5_decrypt(sa->ekey, sa->akeylen, sa->akey, sa->iv, data, length, data, &outlen, 0); if (ret == 0) ipcavium_num_esp_3des_md5++; break; case IPHWCRYPTO_ENGINE_ESP_3DES_CBC_SHA1: if (enc) ret = DES_ede3_cbc_sha1_encrypt(sa->ekey, sa->akeylen, sa->akey, data, iv, data + sa->hdrlen, length - sa->hdrlen, data, &outlen); else ret = DES_ede3_cbc_sha1_decrypt(sa->ekey, sa->akeylen, sa->akey, sa->iv, data, length, data, &outlen, 0); if (ret == 0) ipcavium_num_esp_3des_sha1++; break; case IPHWCRYPTO_ENGINE_ESP_AES_CBC_MD5: if (enc) ret = AES_cbc_md5_encrypt(sa->ekeylen, sa->ekey, sa->akeylen, sa->akey, data, iv, data + sa->hdrlen, length - sa->hdrlen, data, &outlen); else ret = AES_cbc_md5_decrypt(sa->ekeylen, sa->ekey, sa->akeylen, sa->akey, sa->iv, data, length, data, &outlen, 0); if (ret == 0) ipcavium_num_esp_aes_md5++; break; case IPHWCRYPTO_ENGINE_ESP_AES_CBC_SHA1: if (enc) ret = AES_cbc_sha1_encrypt_fast(sa->ekeylen, sa->ekey, data, iv, data + sa->hdrlen, length - sa->hdrlen, data, (Ip_u8 *)&((Ipipsec_sa *)sa->ipsec_sa)->auth->i_ctx, (Ip_u8 *)&((Ipipsec_sa *)sa->ipsec_sa)->auth->o_ctx); else ret = AES_cbc_sha1_decrypt_fast(sa->ekeylen, sa->ekey, sa->iv, data, length, data, (Ip_u8 *)&((Ipipsec_sa *)sa->ipsec_sa)->auth->i_ctx, (Ip_u8 *)&((Ipipsec_sa *)sa->ipsec_sa)->auth->o_ctx); if (ret == 0) ipcavium_num_esp_aes_sha1++; break; default: goto fail; } if (enc) { /* OCTEON places MAC at the end of the packet */ ipcom_memcpy(digest, data + length, sa->authlen); } else { /* OCTEON places MAC at the end of the packet */ ipcom_memcpy(digest, data + length - sa->authlen, sa->authlen); /* Restore original MAC. The IPSec module will verify the MAC */ ipcom_memcpy(data + length - sa->authlen, hmac, sizeof(hmac)); } #ifdef IP_PORT_LKM octeon_crypto_disable(octeon_cop2_state, crypto_flags); #endif return 1; fail: IPCOM_LOG2(ERR, "ipcavium_ipsec_esp_crypto() :: %s error for algorithm %d", enc ? "encrypt" : "decrypt", alg); #ifdef IP_PORT_LKM octeon_crypto_disable(octeon_cop2_state, crypto_flags); #endif return -1; } /* **************************************************************************** * 10 GLOBAL FUNCTIONS **************************************************************************** */ /* *=========================================================================== * ipcavium_ipsec_esp_3des_cbc_md5_input *=========================================================================== * Description: IPSEC ESP input processing using 3DES-CBC-MD5. * Parameters: data - pointer to the packet data. * length - length of the packet. * sa - pointer to the sa (security association). * param - pointer to ipsec parameters. * digest - pointer the message autentication code. * pkt - pointer to the packet structure. * Returns: 2 = kept * 1 = ok * 0 = timeout * -1 = error */ IP_STATIC int ipcavium_ipsec_esp_3des_cbc_md5_input(Ip_u8 *data, Ip_u32 length, Iphwcrypto_esp_cbc_sa *sa, Ip_u8 *digest, Ipipsec_param *param, Ipcom_pkt *pkt) { (void)param; (void)pkt; return ipcavium_ipsec_esp_crypto(data, length, sa, digest, IPHWCRYPTO_ENGINE_ESP_3DES_CBC_MD5, 0); } /* *=========================================================================== * ipcavium_ipsec_esp_3des_cbc_md5_output *=========================================================================== * Description: IPSEC ESP output processing using 3DES-CBC-MD5. * Parameters: data - pointer to the packet data. * length - length of the packet. * sa - pointer to the sa (security association). * param - pointer to ipsec parameters. * digest - pointer the message autentication code. * pkt - pointer to the packet structure. * Returns: 2 = kept * 1 = ok * 0 = timeout * -1 = error */ IP_STATIC int ipcavium_ipsec_esp_3des_cbc_md5_output(Ip_u8 *data, Ip_u32 length, Iphwcrypto_esp_cbc_sa *sa, Ip_u8 *digest, Ipipsec_param *param, Ipcom_pkt *pkt) { (void)param; (void)pkt; return ipcavium_ipsec_esp_crypto(data, length, sa, digest, IPHWCRYPTO_ENGINE_ESP_3DES_CBC_MD5, 1); } /* *=========================================================================== * ipcavium_ipsec_esp_3des_cbc_sha1_input *=========================================================================== * Description: IPSEC ESP input processing using 3DES-CBC-SHA1. * Parameters: data - pointer to the packet data. * length - length of the packet. * sa - pointer to the sa (security association). * param - pointer to ipsec parameters. * digest - pointer the message autentication code. * pkt - pointer to the packet structure. * Returns: 2 = kept * 1 = ok * 0 = timeout * -1 = error */ IP_STATIC int ipcavium_ipsec_esp_3des_cbc_sha1_input(Ip_u8 *data, Ip_u32 length, Iphwcrypto_esp_cbc_sa *sa, Ip_u8 *digest, Ipipsec_param *param, Ipcom_pkt *pkt) { (void)param; (void)pkt; return ipcavium_ipsec_esp_crypto(data, length, sa, digest, IPHWCRYPTO_ENGINE_ESP_3DES_CBC_SHA1, 0); } /* *=========================================================================== * ipcavium_ipsec_esp_3des_cbc_sha1_output *=========================================================================== * Description: IPSEC ESP output processing using 3DES-CBC-SHA1. * Parameters: data - pointer to the packet data. * length - length of the packet. * sa - pointer to the sa (security association). * param - pointer to ipsec parameters. * digest - pointer the message autentication code. * pkt - pointer to the packet structure. * Returns: 2 = kept * 1 = ok * 0 = timeout * -1 = error */ IP_STATIC int ipcavium_ipsec_esp_3des_cbc_sha1_output(Ip_u8 *data, Ip_u32 length, Iphwcrypto_esp_cbc_sa *sa, Ip_u8 *digest, Ipipsec_param *param, Ipcom_pkt *pkt) { (void)param; (void)pkt; return ipcavium_ipsec_esp_crypto(data, length, sa, digest, IPHWCRYPTO_ENGINE_ESP_3DES_CBC_SHA1, 1); } /* *=========================================================================== * ipcavium_ipsec_esp_aes_cbc_md5_input *=========================================================================== * Description: IPSEC ESP input processing using AES-CBC-MD5. * Parameters: data - pointer to the packet data. * length - length of the packet. * sa - pointer to the sa (security association). * param - pointer to ipsec parameters. * digest - pointer the message autentication code. * pkt - pointer to the packet structure. * Returns: 2 = kept * 1 = ok * 0 = timeout * -1 = error */ IP_STATIC int ipcavium_ipsec_esp_aes_cbc_md5_input(Ip_u8 *data, Ip_u32 length, Iphwcrypto_esp_cbc_sa *sa, Ip_u8 *digest, Ipipsec_param *param, Ipcom_pkt *pkt) { (void)param; (void)pkt; return ipcavium_ipsec_esp_crypto(data, length, sa, digest, IPHWCRYPTO_ENGINE_ESP_AES_CBC_MD5, 0); } /* *=========================================================================== * ipcavium_ipsec_esp_aes_cbc_md5_output *=========================================================================== * Description: IPSEC ESP output processing using AES-CBC-MD5. * Parameters: data - pointer to the packet data. * length - length of the packet. * sa - pointer to the sa (security association). * param - pointer to ipsec parameters. * digest - pointer the message autentication code. * pkt - pointer to the packet structure. * Returns: 2 = kept * 1 = ok * 0 = timeout * -1 = error */ IP_STATIC int ipcavium_ipsec_esp_aes_cbc_md5_output(Ip_u8 *data, Ip_u32 length, Iphwcrypto_esp_cbc_sa *sa, Ip_u8 *digest, Ipipsec_param *param, Ipcom_pkt *pkt) { (void)param; (void)pkt; return ipcavium_ipsec_esp_crypto(data, length, sa, digest, IPHWCRYPTO_ENGINE_ESP_AES_CBC_MD5, 1); } /* *=========================================================================== * ipcavium_ipsec_esp_aes_cbc_sha1_input *=========================================================================== * Description: IPSEC ESP input processing using AES-CBC-SHA1. * Parameters: data - pointer to the packet data. * length - length of the packet. * sa - pointer to the sa (security association). * param - pointer to ipsec parameters. * digest - pointer the message autentication code. * pkt - pointer to the packet structure. * Returns: 2 = kept * 1 = ok * 0 = timeout * -1 = error */ IP_STATIC int ipcavium_ipsec_esp_aes_cbc_sha1_input(Ip_u8 *data, Ip_u32 length, Iphwcrypto_esp_cbc_sa *sa, Ip_u8 *digest, Ipipsec_param *param, Ipcom_pkt *pkt) { (void)param; (void)pkt; return ipcavium_ipsec_esp_crypto(data, length, sa, digest, IPHWCRYPTO_ENGINE_ESP_AES_CBC_SHA1, 0); } /* *=========================================================================== * ipcavium_ipsec_esp_aes_cbc_sha1_output *=========================================================================== * Description: IPSEC ESP output processing using AES-CBC-SHA1. * Parameters: data - pointer to the packet data. * length - length of the packet. * sa - pointer to the sa (security association). * param - pointer to ipsec parameters. * digest - pointer the message autentication code. * pkt - pointer to the packet structure. * Returns: 2 = kept * 1 = ok * 0 = timeout * -1 = error */ IP_STATIC int ipcavium_ipsec_esp_aes_cbc_sha1_output(Ip_u8 *data, Ip_u32 length, Iphwcrypto_esp_cbc_sa *sa, Ip_u8 *digest, Ipipsec_param *param, Ipcom_pkt *pkt) { (void)param; (void)pkt; return ipcavium_ipsec_esp_crypto(data, length, sa, digest, IPHWCRYPTO_ENGINE_ESP_AES_CBC_SHA1, 1); } #endif /* IPIPSEC2 */ /* **************************************************************************** * 11 PUBLIC FUNCTIONS **************************************************************************** */ /* *=========================================================================== * ipcavium_cmd_cavium *=========================================================================== * Description: cavium shell command. * Parameters: argc - number of arguments. * argv - pointer to array of arguments. * Returns: 0 = success * -1 = fail */ IP_PUBLIC int ipcavium_cmd_cavium(int argc, char **argv) { if (argc < 2) { usage: ipcom_printf("Usage: cavium "IP_LF); ipcom_printf(IP_LF); ipcom_printf(" Commands: stat - Print stats"IP_LF); ipcom_printf(" clr - Clear stats"IP_LF); return 0; } if (ipcom_strcmp(argv[1], "clr") == 0) { ipcavium_num_esp_3des_md5 = 0; ipcavium_num_esp_3des_sha1 = 0; ipcavium_num_esp_aes_md5 = 0; ipcavium_num_esp_aes_sha1 = 0; } else if (ipcom_strcmp(argv[1], "stat") == 0) { ipcom_printf("ESP-MD5-3DES --- %d\n", ipcavium_num_esp_3des_md5); ipcom_printf("ESP-SHA1-3DES -- %d\n", ipcavium_num_esp_3des_sha1); ipcom_printf("ESP-MD5-AES ---- %d\n", ipcavium_num_esp_aes_md5); ipcom_printf("ESP-SHA1-AES --- %d\n", ipcavium_num_esp_aes_sha1); } else goto usage; return 0; } /* *=========================================================================== * ipcavium_version *=========================================================================== * Description: Returns the version string of the product. * Parameters: none. * Returns: pointer to the version string. * */ IP_PUBLIC const char * ipcavium_version(void) { return "@(#) IPCAVIUM $Name: VXWORKS_ITER40_2019102207 $ - INTERPEAK_COPYRIGHT_STRING"; } /* *=========================================================================== * ipcavium_create *=========================================================================== * Description: Product create hook. Called by IPCOM at startup. * Parameters: none. * Returns: IPCOM_SUCCESS = ok * IPCOM_ERR_... = error * */ IP_PUBLIC Ip_err ipcavium_create(void) { return IPCOM_SUCCESS; } /* *=========================================================================== * ipcavium_configure *=========================================================================== * Description: Product configure hook. Called by IPCOM at startup. * Parameters: none. * Returns: IPCOM_SUCCESS = ok * IPCOM_ERR_FAILED = error * */ #ifndef IP_PORT_VXWORKS IP_PUBLIC Ip_err ipcavium_configure(void) { return IPCOM_SUCCESS; } #endif /* *=========================================================================== * ipcavium_start *=========================================================================== * Description: Product start hook. Called by IPCOM at startup. * Parameters: none. * Returns: IPCOM_SUCCESS = ok * IPCOM_ERR_FAILED = error * */ IP_PUBLIC Ip_err ipcavium_start(void) { Ip_err ret; const char *engine_id = "octeon"; const char *engine_name = "Cavium OCTEON hardware crypto support"; /* Add an engine to iphwcrypto */ ret = iphwcrypto_engine_add(engine_id, engine_name); if (ret != IPCOM_SUCCESS) { IPCOM_LOG1(ERR, "ipcavium_create() :: iphwcrypto_engine_add() failed with error %s", ipcom_err_string(ret)); return ret; } #ifdef IPIPSEC2 /* 3DES-CBC-MD5 */ ret = iphwcrypto_ipsec_engine_add_esp(engine_id, IPHWCRYPTO_ENGINE_ESP_3DES_CBC_MD5, ipcavium_ipsec_esp_3des_cbc_md5_input, ipcavium_ipsec_esp_3des_cbc_md5_output); if (ret != IPCOM_SUCCESS) { IPCOM_LOG1(ERR, "ipcavium_create() :: iphwcrypto_ipsec_engine_add_esp(IPHWCRYPTO_ENGINE_ESP_3DES_CBC_MD5) failed with error %s", ipcom_err_string(ret)); return ret; } /* 3DES-CBC-SHA1 */ ret = iphwcrypto_ipsec_engine_add_esp(engine_id, IPHWCRYPTO_ENGINE_ESP_3DES_CBC_SHA1, ipcavium_ipsec_esp_3des_cbc_sha1_input, ipcavium_ipsec_esp_3des_cbc_sha1_output); if (ret != IPCOM_SUCCESS) { IPCOM_LOG1(ERR, "ipcavium_create() :: iphwcrypto_ipsec_engine_add_esp(IPHWCRYPTO_ENGINE_ESP_3DES_CBC_SHA1) failed with error %s", ipcom_err_string(ret)); return ret; } /* AES-CBC-MD5 */ ret = iphwcrypto_ipsec_engine_add_esp(engine_id, IPHWCRYPTO_ENGINE_ESP_AES_CBC_MD5, ipcavium_ipsec_esp_aes_cbc_md5_input, ipcavium_ipsec_esp_aes_cbc_md5_output); if (ret != IPCOM_SUCCESS) { IPCOM_LOG1(ERR, "ipcavium_create() :: iphwcrypto_ipsec_engine_add_esp(IPHWCRYPTO_ENGINE_ESP_AES_CBC_MD5) failed with error %s", ipcom_err_string(ret)); return ret; } /* AES-CBC-SHA1 */ ret = iphwcrypto_ipsec_engine_add_esp(engine_id, IPHWCRYPTO_ENGINE_ESP_AES_CBC_SHA1, ipcavium_ipsec_esp_aes_cbc_sha1_input, ipcavium_ipsec_esp_aes_cbc_sha1_output); if (ret != IPCOM_SUCCESS) { IPCOM_LOG1(ERR, "ipcavium_create() :: iphwcrypto_ipsec_engine_add_esp(IPHWCRYPTO_ENGINE_ESP_AES_CBC_SHA1) failed with error %s", ipcom_err_string(ret)); return ret; } /* Register esp handlers */ ret = iphwcrypto_ipsec_engine_register_esp(engine_id); if (ret != IPCOM_SUCCESS) { IPCOM_LOG1(ERR, "ipcavium_create() :: iphwcrypto_engine_register_esp() failed with error %s", ipcom_err_string(ret)); return ret; } #endif /* IPIPSEC2 */ return IPCOM_SUCCESS; } /* **************************************************************************** * END OF FILE **************************************************************************** */