Ram Gandikota / Mbed OS ABCD
Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers m2mconnectionhandlerpimpl.cpp Source File

m2mconnectionhandlerpimpl.cpp

00001 /*
00002  * Copyright (c) 2015 ARM Limited. All rights reserved.
00003  * SPDX-License-Identifier: Apache-2.0
00004  * Licensed under the Apache License, Version 2.0 (the License); you may
00005  * not use this file except in compliance with the License.
00006  * You may obtain a copy of the License at
00007  *
00008  * http://www.apache.org/licenses/LICENSE-2.0
00009  *
00010  * Unless required by applicable law or agreed to in writing, software
00011  * distributed under the License is distributed on an AS IS BASIS, WITHOUT
00012  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00013  * See the License for the specific language governing permissions and
00014  * limitations under the License.
00015  */
00016 #include "mbed-client-classic/m2mconnectionhandlerpimpl.h"
00017 #include "mbed-client/m2mconnectionobserver.h"
00018 #include "mbed-client/m2mconstants.h"
00019 #include "mbed-client/m2msecurity.h"
00020 #include "mbed-client/m2mconnectionhandler.h"
00021 
00022 #include "pal.h"
00023 #include "pal_rtos.h"
00024 #include "pal_errors.h"
00025 #include "pal_macros.h"
00026 #include "pal_network.h"
00027 
00028 #include "eventOS_scheduler.h"
00029 #include "eventOS_event.h"
00030 
00031 #include "mbed-trace/mbed_trace.h"
00032 #include "mbed.h"
00033 
00034 #define TRACE_GROUP "mClt"
00035 
00036 int8_t M2MConnectionHandlerPimpl::_tasklet_id = -1;
00037 
00038 static M2MConnectionHandlerPimpl *connection_handler = NULL;
00039 
00040 extern "C" void connection_event_handler(arm_event_s *event)
00041 {
00042     if(!connection_handler){
00043         return;
00044     }
00045 
00046     switch(event->event_type){
00047         case M2MConnectionHandlerPimpl::ESocketReadytoRead:
00048             connection_handler->receive_handler();
00049             break;
00050 
00051         case M2MConnectionHandlerPimpl::ESocketSend:
00052             connection_handler->send_socket_data((uint8_t*)event->data_ptr, event->event_data);
00053             free(event->data_ptr);
00054             break;
00055 
00056         case M2MConnectionHandlerPimpl::ESocketDnsHandler:
00057             connection_handler->dns_handler();
00058             break;
00059 
00060         default:
00061             break;
00062     }
00063 }
00064 
00065 void M2MConnectionHandlerPimpl::send_receive_event(void)
00066 {
00067     arm_event_s event;
00068     event.receiver = M2MConnectionHandlerPimpl::_tasklet_id;
00069     event.sender = 0;
00070     event.event_type = ESocketReadytoRead;
00071     event.data_ptr = NULL;
00072     event.priority = ARM_LIB_HIGH_PRIORITY_EVENT;
00073     eventOS_event_send(&event);
00074 }
00075 
00076 extern "C" void socket_event_handler(void)
00077 {
00078     if(!connection_handler) {
00079         return;
00080     }
00081     connection_handler->send_receive_event();
00082 }
00083 
00084 M2MConnectionHandlerPimpl::M2MConnectionHandlerPimpl(M2MConnectionHandler* base, M2MConnectionObserver &observer,
00085                                                      M2MConnectionSecurity* sec,
00086                                                      M2MInterface::BindingMode mode,
00087                                                      M2MInterface::NetworkStack stack)
00088 :_base(base),
00089  _observer(observer),
00090  _security_impl(sec),
00091  _security(NULL),
00092  _use_secure_connection(false),
00093  _binding_mode(mode),
00094  _network_stack(stack),
00095  _socket(0),
00096  _is_handshaking(false),
00097  _listening(true),
00098  _server_type(M2MConnectionObserver::LWM2MServer),
00099  _server_port(0),
00100  _listen_port(0),
00101  _running(false),
00102  _net_iface(0)
00103 {
00104 #ifndef PAL_NET_TCP_AND_TLS_SUPPORT
00105     if (is_tcp_connection()) {
00106         tr_error("ConnectionHandler: TCP support not available.");
00107         return;
00108     }
00109 #endif
00110 
00111     if(PAL_SUCCESS != pal_init()){
00112         tr_error("PAL init failed.");
00113     }
00114 
00115     memset(&_address, 0, sizeof _address);
00116     memset(&_socket_address, 0, sizeof _socket_address);
00117     connection_handler = this;
00118     eventOS_scheduler_mutex_wait();
00119     if (M2MConnectionHandlerPimpl::_tasklet_id == -1) {
00120         M2MConnectionHandlerPimpl::_tasklet_id = eventOS_event_handler_create(&connection_event_handler, ESocketIdle);
00121     }
00122     eventOS_scheduler_mutex_release();
00123 }
00124 
00125 M2MConnectionHandlerPimpl::~M2MConnectionHandlerPimpl()
00126 {
00127     tr_debug("~M2MConnectionHandlerPimpl()");
00128     stop_listening();
00129 
00130     close_socket();
00131 
00132     delete _security_impl;
00133     tr_debug("~M2MConnectionHandlerPimpl() - OUT");
00134 }
00135 
00136 bool M2MConnectionHandlerPimpl::bind_connection(const uint16_t listen_port)
00137 {
00138     _listen_port = listen_port;
00139     return true;
00140 }
00141 
00142 bool M2MConnectionHandlerPimpl::resolve_server_address(const String& server_address,
00143                                                        const uint16_t server_port,
00144                                                        M2MConnectionObserver::ServerType server_type,
00145                                                        const M2MSecurity* security)
00146 {
00147     arm_event_s event;
00148 
00149     tr_debug("resolve_server_address()");
00150 
00151     _security = security;
00152     _server_port = server_port;
00153     _server_type = server_type;
00154     _server_address = server_address;
00155 
00156     event.receiver = M2MConnectionHandlerPimpl::_tasklet_id;
00157     event.sender = 0;
00158     event.event_type = ESocketDnsHandler;
00159     event.data_ptr = NULL;
00160     event.priority = ARM_LIB_HIGH_PRIORITY_EVENT;
00161 
00162     return !eventOS_event_send(&event);
00163 }
00164 
00165 void M2MConnectionHandlerPimpl::dns_handler()
00166 {
00167     palStatus_t status;
00168     uint32_t interface_count;
00169     status = pal_getNumberOfNetInterfaces(&interface_count);
00170     if(PAL_SUCCESS != status ) {
00171         _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT);
00172         return;
00173     }
00174     if(interface_count <= 0) {
00175         _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT);
00176         return;
00177     }
00178 
00179     palSocketLength_t _socket_address_len;
00180 
00181     if(PAL_SUCCESS != pal_getAddressInfo(_server_address.c_str(), &_socket_address, &_socket_address_len)){
00182         _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT);
00183         return;
00184     }
00185     pal_setSockAddrPort(&_socket_address, _server_port);
00186 
00187     if(_network_stack == M2MInterface::LwIP_IPv4 ||
00188        _network_stack == M2MInterface::ATWINC_IPv4){
00189         if(PAL_SUCCESS != pal_getSockAddrIPV4Addr(&_socket_address,_ipV4Addr)){
00190             _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT);
00191             return;
00192         }
00193 
00194         tr_debug("IP Address %s",tr_array(_ipV4Addr, 4));
00195 
00196         _address._address = (void*)_ipV4Addr;
00197         _address._length = PAL_IPV4_ADDRESS_SIZE;
00198         _address._port = _server_port;
00199         _address._stack = _network_stack;
00200     }
00201     else if(_network_stack == M2MInterface::LwIP_IPv6 ||
00202             _network_stack == M2MInterface::Nanostack_IPv6){
00203         if(PAL_SUCCESS != pal_getSockAddrIPV6Addr(&_socket_address,_ipV6Addr)){
00204             _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT);
00205             return;
00206         }
00207 
00208         tr_debug("IP Address %s",tr_array(_ipV6Addr,sizeof(_ipV6Addr)));
00209 
00210         _address._address = (void*)_ipV6Addr;
00211         _address._length = PAL_IPV6_ADDRESS_SIZE;
00212         _address._port = _server_port;
00213         _address._stack = _network_stack;
00214     }
00215     else {
00216         tr_error("socket config error, %d", (int)_network_stack);
00217         _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT);
00218         return;
00219     }
00220 
00221     close_socket();
00222     if(!init_socket()) {
00223         _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT);
00224         return;
00225     }
00226 
00227     if(is_tcp_connection()) {
00228 #ifdef PAL_NET_TCP_AND_TLS_SUPPORT
00229        tr_debug("resolve_server_address - Using TCP");
00230         if (pal_connect(_socket, &_socket_address, sizeof(_socket_address)) != PAL_SUCCESS) {
00231             _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT);
00232             return;
00233         }
00234 #endif //PAL_NET_TCP_AND_TLS_SUPPORT
00235     }
00236 
00237     _running = true;
00238 
00239     if (_security) {
00240         if (_security->resource_value_int(M2MSecurity::SecurityMode) == M2MSecurity::Certificate ||
00241             _security->resource_value_int(M2MSecurity::SecurityMode) == M2MSecurity::Psk) {
00242             if( _security_impl != NULL ){
00243                 _security_impl->reset();
00244                 if (_security_impl->init(_security) == 0) {
00245                     _is_handshaking = true;
00246                     tr_debug("resolve_server_address - connect DTLS");
00247                     if(_security_impl->start_connecting_non_blocking(_base) < 0 ){
00248                         tr_debug("dns_handler - handshake failed");
00249                         _is_handshaking = false;
00250                         _observer.socket_error(M2MConnectionHandler::SSL_CONNECTION_ERROR);
00251                         close_socket();
00252                         return;
00253                     }
00254                 } else {
00255                     tr_error("resolve_server_address - init failed");
00256                     _observer.socket_error(M2MConnectionHandler::SSL_CONNECTION_ERROR, false);
00257                     close_socket();
00258                     return;
00259                 }
00260             } else {
00261                 tr_error("dns_handler - sec is null");
00262                 _observer.socket_error(M2MConnectionHandler::SSL_CONNECTION_ERROR, false);
00263                 close_socket();
00264                 return;
00265             }
00266         }
00267     }
00268     if(!_is_handshaking) {
00269         enable_keepalive();
00270         _observer.address_ready(_address,
00271                                 _server_type,
00272                                 _address._port);
00273     }
00274 }
00275 
00276 bool M2MConnectionHandlerPimpl::send_data(uint8_t *data,
00277                                           uint16_t data_len,
00278                                           sn_nsdl_addr_s *address)
00279 {
00280     arm_event_s event;
00281 
00282     tr_debug("send_data()");
00283     if (address == NULL || data == NULL || !data_len || !_running) {
00284         return false;
00285     }
00286 
00287     event.data_ptr = (uint8_t*)malloc(data_len);
00288     if(!event.data_ptr) {
00289         return false;
00290     }
00291     memcpy(event.data_ptr, data, data_len);
00292 
00293     event.receiver = M2MConnectionHandlerPimpl::_tasklet_id;
00294     event.sender = 0;
00295     event.event_type = ESocketSend;
00296     event.event_data = data_len;
00297     event.priority = ARM_LIB_HIGH_PRIORITY_EVENT;
00298 
00299     if (eventOS_event_send(&event) != 0) {
00300         // Event push failed, free the buffer
00301         free(event.data_ptr);
00302         return false;
00303     }
00304 
00305     return true;
00306 }
00307 
00308 void M2MConnectionHandlerPimpl::send_socket_data(uint8_t *data, uint16_t data_len)
00309 {
00310     size_t sent_len;
00311     bool success = false;
00312     palStatus_t ret = PAL_ERR_GENERIC_FAILURE;
00313 
00314     if(!data || ! data_len || !_running) {
00315         return;
00316     }
00317 
00318     tr_debug("send_handler()");
00319 
00320     if( _use_secure_connection ){
00321         if( _security_impl->send_message(data, data_len) > 0){
00322             success = true;
00323         }
00324     } else {
00325         if(is_tcp_connection()){
00326 #ifdef PAL_NET_TCP_AND_TLS_SUPPORT
00327             //We need to "shim" the length in front
00328             uint8_t* d = (uint8_t*)malloc(data_len+4);
00329             if(d){
00330                 d[0] = 0;
00331                 d[1] = 0;
00332                 d[2] = (data_len >> 8 )& 0xff;
00333                 d[3] = data_len & 0xff;
00334                 memcpy(d + 4, data, data_len);
00335                 ret = pal_send(_socket, d, data_len+4, &sent_len);
00336                 free(d);
00337             }
00338 #endif //PAL_NET_TCP_AND_TLS_SUPPORT
00339         } else {
00340             ret = pal_sendTo(_socket, data, data_len, &_socket_address, sizeof(_socket_address), &sent_len);
00341         }
00342         if (ret == PAL_SUCCESS) {
00343             success = true;
00344         }
00345     }
00346 
00347     if (!success) {
00348         _observer.socket_error(M2MConnectionHandler::SOCKET_SEND_ERROR, true);
00349         close_socket();
00350     }
00351     else{
00352         _observer.data_sent();
00353     }
00354 }
00355 
00356 bool M2MConnectionHandlerPimpl::start_listening_for_data()
00357 {
00358     tr_debug("start_listening_for_data()");
00359     _listening = true;
00360     return true;
00361 }
00362 
00363 void M2MConnectionHandlerPimpl::stop_listening()
00364 {
00365     tr_debug("stop_listening()");
00366     _listening = false;
00367 
00368     if(_security_impl) {
00369         _security_impl->reset();
00370     }
00371 }
00372 
00373 int M2MConnectionHandlerPimpl::send_to_socket(const unsigned char *buf, size_t len)
00374 {
00375     size_t sent_len = 0;
00376     palStatus_t status = PAL_ERR_GENERIC_FAILURE;
00377 
00378     if(!_running) {
00379         return (-1);
00380     }
00381 
00382     tr_debug("send_to_socket len - %d", len);
00383 
00384     if(is_tcp_connection()) {
00385 #ifdef PAL_NET_TCP_AND_TLS_SUPPORT
00386         status = pal_send(_socket, buf, len, &sent_len);
00387 #endif //PAL_NET_TCP_AND_TLS_SUPPORT
00388     } else {
00389         status = pal_sendTo(_socket, buf, len, &_socket_address, sizeof(_socket_address), &sent_len);
00390     }
00391 
00392     if(status == PAL_SUCCESS){
00393         return sent_len;
00394     }
00395 
00396     return (-1);
00397 }
00398 
00399 int M2MConnectionHandlerPimpl::receive_from_socket(unsigned char *buf, size_t len)
00400 {
00401     size_t recv_len;
00402     palStatus_t status = PAL_ERR_GENERIC_FAILURE;
00403     tr_debug("receive_from_socket");
00404 
00405     if(!_running) {
00406         return (-1);
00407     }
00408 
00409     if(is_tcp_connection()) {
00410 #ifdef PAL_NET_TCP_AND_TLS_SUPPORT
00411         status = pal_recv(_socket, buf, len, &recv_len);
00412 #endif //PAL_NET_TCP_AND_TLS_SUPPORT
00413     } else {
00414         status = pal_receiveFrom(_socket, buf, len, NULL, NULL, &recv_len);
00415     }
00416 
00417     if(status == PAL_SUCCESS){
00418         return recv_len;
00419     }
00420     else if(status == PAL_ERR_SOCKET_WOULD_BLOCK || status == (-65536)){
00421         return M2MConnectionHandler::CONNECTION_ERROR_WANTS_READ;
00422     }
00423     else {
00424         tr_info("PAL Socket returned: %d", status);
00425     }
00426 
00427     return (-1);
00428 }
00429 
00430 void M2MConnectionHandlerPimpl::handle_connection_error(int error)
00431 {
00432     tr_debug("handle_connection_error");
00433     _observer.socket_error(error);
00434 }
00435 
00436 void M2MConnectionHandlerPimpl::set_platform_network_handler(void *handler)
00437 {
00438     tr_debug("set_platform_network_handler");
00439     if(PAL_SUCCESS != pal_registerNetworkInterface(handler, &_net_iface)) {
00440         tr_error("Interface registration failed.");
00441     }
00442 }
00443 
00444 void M2MConnectionHandlerPimpl::receive_handshake_handler()
00445 {
00446     tr_debug("receive_handshake_handler()");
00447     if( _is_handshaking ){
00448         int ret = _security_impl->continue_connecting();
00449         tr_debug("ret %d", ret);
00450         if( ret == M2MConnectionHandler::CONNECTION_ERROR_WANTS_READ ){ //We wait for next readable event
00451             tr_debug("We wait for next readable event");
00452             return;
00453         } else if( ret == 0 ){
00454             _is_handshaking = false;
00455             _use_secure_connection = true;
00456             enable_keepalive();
00457             _observer.address_ready(_address,
00458                                     _server_type,
00459                                     _server_port);
00460         } else if( ret < 0 ){
00461             _is_handshaking = false;
00462             _observer.socket_error(M2MConnectionHandler::SSL_CONNECTION_ERROR, true);
00463             close_socket();
00464         }
00465     }
00466 }
00467 
00468 bool M2MConnectionHandlerPimpl::is_handshake_ongoing()
00469 {
00470     return _is_handshaking;
00471 }
00472 
00473 void M2MConnectionHandlerPimpl::receive_handler()
00474 {
00475     tr_debug("receive_handler()");
00476     if(_is_handshaking){
00477         receive_handshake_handler();
00478         return;
00479     }
00480 
00481     if(!_listening || !_running) {
00482         return;
00483     }
00484 
00485     if( _use_secure_connection ){
00486         int rcv_size;
00487         do{
00488             rcv_size = _security_impl->read(_recv_buffer, sizeof(_recv_buffer));
00489             if(rcv_size > 0){
00490                 _observer.data_available((uint8_t*)_recv_buffer,
00491                                          rcv_size, _address);
00492             } else if (M2MConnectionHandler::CONNECTION_ERROR_WANTS_READ != rcv_size && rcv_size < 0) {
00493                 _observer.socket_error(M2MConnectionHandler::SOCKET_READ_ERROR, true);
00494                 close_socket();
00495                 return;
00496             }
00497         } while(M2MConnectionHandler::CONNECTION_ERROR_WANTS_READ != rcv_size);
00498     } else{
00499         size_t recv;
00500         palStatus_t status;
00501         do{
00502             if(is_tcp_connection()){
00503 #ifdef PAL_NET_TCP_AND_TLS_SUPPORT
00504                 status = pal_recv(_socket, _recv_buffer, sizeof(_recv_buffer), &recv);
00505 #endif //PAL_NET_TCP_AND_TLS_SUPPORT
00506             } else{
00507                 status = pal_receiveFrom(_socket, _recv_buffer, sizeof(_recv_buffer), NULL, NULL, &recv);
00508             }
00509 
00510             if(status == PAL_ERR_SOCKET_WOULD_BLOCK){
00511                 return;
00512             }
00513             else if (status != PAL_SUCCESS) {
00514                 _observer.socket_error(M2MConnectionHandler::SOCKET_READ_ERROR, true);
00515                 close_socket();
00516                 return;
00517             }
00518 
00519             tr_debug("data received, len: %zu", recv);
00520 
00521             if(!is_tcp_connection()){ // Observer for UDP plain mode
00522                 _observer.data_available((uint8_t*)_recv_buffer, recv, _address);
00523             } else {
00524 #ifdef PAL_NET_TCP_AND_TLS_SUPPORT
00525                 if( recv < 4 ){
00526                     _observer.socket_error(M2MConnectionHandler::SOCKET_READ_ERROR, true);
00527                     close_socket();
00528                     return;
00529                 }
00530 
00531                 //We need to "shim" out the length from the front
00532                 uint32_t len = (_recv_buffer[0] << 24 & 0xFF000000) + (_recv_buffer[1] << 16 & 0xFF0000);
00533                 len += (_recv_buffer[2] << 8 & 0xFF00) + (_recv_buffer[3] & 0xFF);
00534                 if(len > 0 && len <= recv - 4) {
00535                     // Observer for TCP plain mode
00536                     _observer.data_available(_recv_buffer + 4, len, _address);
00537                 }
00538 #endif //PAL_NET_TCP_AND_TLS_SUPPORT
00539             }
00540         } while(status != PAL_ERR_SOCKET_WOULD_BLOCK);
00541     }
00542 }
00543 
00544 void M2MConnectionHandlerPimpl::claim_mutex()
00545 {
00546     eventOS_scheduler_mutex_wait();
00547 }
00548 
00549 void M2MConnectionHandlerPimpl::release_mutex()
00550 {
00551     eventOS_scheduler_mutex_release();
00552 }
00553 
00554 static palNetInterfaceInfo_t  interface_info;
00555 static palIpV4Addr_t interface_address4 = {0,0,0,0};
00556 static palIpV6Addr_t interface_address6 = {0};
00557 
00558 bool M2MConnectionHandlerPimpl::init_socket()
00559 {
00560     tr_debug("init_socket - IN");
00561     _is_handshaking = false;
00562     _running = true;
00563     palSocketType_t socket_type = PAL_SOCK_DGRAM;
00564     palStatus_t status;
00565     palSocketDomain_t domain;
00566     palSocketAddress_t bind_address;
00567 
00568     if(is_tcp_connection()) {
00569 #ifdef PAL_NET_TCP_AND_TLS_SUPPORT
00570         socket_type = PAL_SOCK_STREAM;
00571 #else
00572         _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT);
00573         return;
00574 #endif //PAL_NET_TCP_AND_TLS_SUPPORT
00575     }
00576 
00577     if(_network_stack == M2MInterface::LwIP_IPv4){
00578         domain = PAL_AF_INET;
00579     } else if(_network_stack == M2MInterface::LwIP_IPv6){
00580         domain = PAL_AF_INET6;
00581     } else {
00582         domain = PAL_AF_UNSPEC;
00583     }
00584 
00585     uint32_t interface_count;
00586     pal_getNumberOfNetInterfaces(&interface_count);
00587     tr_debug("Interface count: %d",interface_count);
00588     pal_getNetInterfaceInfo(_net_iface, &interface_info);
00589     tr_debug("Interface name: %s",interface_info.interfaceName);
00590     tr_debug("Interface no: %d", _net_iface);
00591 
00592     tr_debug("init_socket - port %d", _listen_port);
00593 
00594     status = pal_asynchronousSocket(domain, socket_type, 1, _net_iface, &socket_event_handler, &_socket);
00595 
00596     if(PAL_SUCCESS != status) {
00597         _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT);
00598         return false;
00599     }
00600 
00601     if(_network_stack == M2MInterface::LwIP_IPv4){
00602         pal_setSockAddrIPV4Addr(&bind_address, interface_address4);
00603     } else if(_network_stack == M2MInterface::LwIP_IPv6){
00604         pal_setSockAddrIPV6Addr(&bind_address, interface_address6);
00605     }
00606 
00607     pal_setSockAddrPort(&bind_address, _listen_port);
00608     pal_bind(_socket, &bind_address, sizeof(bind_address));
00609 
00610     tr_debug("init_socket - OUT");
00611     return true;
00612 }
00613 
00614 bool M2MConnectionHandlerPimpl::is_tcp_connection()
00615 {
00616     return ( _binding_mode == M2MInterface::TCP ||
00617              _binding_mode == M2MInterface::TCP_QUEUE );
00618 }
00619 
00620 void M2MConnectionHandlerPimpl::close_socket()
00621 {
00622     tr_debug("close_socket() - IN");
00623     if(_running) {
00624        _running = false;
00625        pal_close(&_socket);
00626     }
00627     tr_debug("close_socket() - OUT");
00628 }
00629 
00630 void M2MConnectionHandlerPimpl::enable_keepalive()
00631 {
00632 #if MBED_CLIENT_TCP_KEEPALIVE_TIME
00633 #ifdef PAL_NET_TCP_AND_TLS_SUPPORT
00634     if(is_tcp_connection()) {
00635         int enable = 1;
00636         pal_setSocketOptions(_socket, PAL_SO_KEEPALIVE, &enable, sizeof(enable));
00637     }
00638 #endif
00639 #endif
00640 }