Magnificent7 / Hackathon
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 "NetworkInterface.h"
00023 #include "UDPSocket.h"
00024 #include "TCPSocket.h"
00025 
00026 #include "eventOS_event.h"
00027 #include "eventOS_scheduler.h"
00028 
00029 #include "mbed-trace/mbed_trace.h"
00030 #include "mbed.h"
00031 
00032 #define TRACE_GROUP "mClt"
00033 
00034 #ifdef MBED_CONF_MBED_CLIENT_EVENT_LOOP_SIZE
00035 #define MBED_CLIENT_EVENT_LOOP_SIZE MBED_CONF_MBED_CLIENT_EVENT_LOOP_SIZE
00036 #else
00037 #define MBED_CLIENT_EVENT_LOOP_SIZE 1024
00038 #endif
00039 
00040 int8_t M2MConnectionHandlerPimpl::_tasklet_id = -1;
00041 
00042 static MemoryPool<M2MConnectionHandlerPimpl::TaskIdentifier, MBED_CLIENT_EVENT_LOOP_SIZE/64> memory_pool;
00043 
00044 extern "C" void connection_tasklet_event_handler(arm_event_s *event)
00045 {
00046     tr_debug("M2MConnectionHandlerPimpl::connection_tasklet_event_handler");
00047     M2MConnectionHandlerPimpl::TaskIdentifier *task_id = (M2MConnectionHandlerPimpl::TaskIdentifier*)event->data_ptr;
00048     M2MConnectionHandlerPimpl* pimpl = (M2MConnectionHandlerPimpl*)task_id->pimpl;
00049     if(pimpl) {
00050         eventOS_scheduler_set_active_tasklet(pimpl->connection_tasklet_handler());
00051     }
00052     switch (event->event_type) {
00053         case M2MConnectionHandlerPimpl::ESocketIdle:
00054             tr_debug("Connection Tasklet Generated");
00055             break;
00056         case M2MConnectionHandlerPimpl::ESocketReadytoRead:
00057             tr_debug("connection_tasklet_event_handler - ESocketReadytoRead");
00058             if(pimpl) {
00059                 if(pimpl->is_handshake_ongoing()) {
00060                     pimpl->receive_handshake_handler();
00061                 } else {
00062                     pimpl->receive_handler();
00063                 }
00064             }
00065             break;
00066         case M2MConnectionHandlerPimpl::ESocketDnsHandler:
00067             tr_debug("connection_tasklet_event_handler - ESocketDnsHandler");
00068             if(pimpl) {
00069                 pimpl->dns_handler();
00070             }
00071             break;
00072         case M2MConnectionHandlerPimpl::ESocketSend:
00073             tr_debug("connection_tasklet_event_handler - ESocketSend");
00074             if(pimpl) {
00075                 pimpl->send_socket_data((uint8_t*)task_id->data_ptr,(uint16_t)event->event_data);
00076                 if (task_id->data_ptr) {
00077                     free(task_id->data_ptr);
00078                 }
00079             }
00080             break;
00081         default:
00082             break;
00083     }
00084     if (task_id) {
00085         memory_pool.free(task_id);
00086     }
00087 }
00088 
00089 M2MConnectionHandlerPimpl::M2MConnectionHandlerPimpl(M2MConnectionHandler* base, M2MConnectionObserver &observer,
00090                                                      M2MConnectionSecurity* sec,
00091                                                      M2MInterface::BindingMode mode,
00092                                                      M2MInterface::NetworkStack stack)
00093 :_base(base),
00094  _observer(observer),
00095  _security_impl(sec),
00096  _use_secure_connection(false),
00097  _binding_mode(mode),
00098  _network_stack(stack),
00099  _socket(0),
00100  _is_handshaking(false),
00101  _listening(true),
00102  _server_type(M2MConnectionObserver::LWM2MServer),
00103  _server_port(0),
00104  _listen_port(0),
00105  _running(false),
00106  _net_iface(0),
00107  _socket_address(0)
00108 {
00109     memset(&_address_buffer, 0, sizeof _address_buffer);
00110     memset(&_address, 0, sizeof _address);
00111     _address._address = _address_buffer;
00112 
00113     if (_network_stack != M2MInterface::LwIP_IPv4) {
00114         tr_error("ConnectionHandler: Unsupported network stack, only IPv4 is currently supported");
00115     }
00116     _running = true;
00117     tr_debug("M2MConnectionHandlerPimpl::M2MConnectionHandlerPimpl() - Initializing thread");
00118     eventOS_scheduler_mutex_wait();
00119     if (M2MConnectionHandlerPimpl::_tasklet_id == -1) {
00120         M2MConnectionHandlerPimpl::_tasklet_id = eventOS_event_handler_create(&connection_tasklet_event_handler, ESocketIdle);
00121     }
00122     eventOS_scheduler_mutex_release();
00123 }
00124 
00125 M2MConnectionHandlerPimpl::~M2MConnectionHandlerPimpl()
00126 {
00127     tr_debug("M2MConnectionHandlerPimpl::~M2MConnectionHandlerPimpl()");
00128     if(_socket_address) {
00129         delete _socket_address;
00130         _socket_address = NULL;
00131     }
00132     if (_socket) {
00133         delete _socket;
00134         _socket = 0;
00135     }
00136     _net_iface = 0;
00137     delete _security_impl;
00138     tr_debug("M2MConnectionHandlerPimpl::~M2MConnectionHandlerPimpl() - OUT");
00139 }
00140 
00141 bool M2MConnectionHandlerPimpl::bind_connection(const uint16_t listen_port)
00142 {
00143     _listen_port = listen_port;
00144     return true;
00145 }
00146 
00147 bool M2MConnectionHandlerPimpl::resolve_server_address(const String& server_address,
00148                                                        const uint16_t server_port,
00149                                                        M2MConnectionObserver::ServerType server_type,
00150                                                        const M2MSecurity* security)
00151 {
00152     tr_debug("M2MConnectionHandlerPimpl::resolve_server_address()");
00153     if (!_net_iface) {
00154         return false;
00155     }
00156     _security = security;
00157     _server_port = server_port;
00158     _server_type = server_type;
00159     _server_address = server_address;
00160     TaskIdentifier* task = memory_pool.alloc();
00161     if (!task) {
00162         return false;
00163     }
00164     task->pimpl = this;
00165 
00166     arm_event_s event;
00167     event.receiver = M2MConnectionHandlerPimpl::_tasklet_id;
00168     event.sender = 0;
00169     event.event_type = ESocketDnsHandler;
00170     event.data_ptr = task;
00171     event.priority = ARM_LIB_HIGH_PRIORITY_EVENT;
00172     return eventOS_event_send(&event) == 0 ? true : false;
00173 }
00174 
00175 void M2MConnectionHandlerPimpl::dns_handler()
00176 {
00177     tr_debug("M2MConnectionHandlerPimpl::dns_handler()");
00178     if(_socket_address) {
00179         delete _socket_address;
00180        _socket_address = NULL;
00181     }
00182     _socket_address = new SocketAddress(_net_iface,_server_address.c_str(), _server_port);
00183     if(*_socket_address) {
00184         _address._address = (void*)_socket_address->get_ip_address();
00185         tr_debug("IP Address %s",_socket_address->get_ip_address());
00186         tr_debug("Port %d",_socket_address->get_port());
00187         _address._length = strlen((char*)_address._address);
00188         _address._port = _socket_address->get_port();
00189         _address._stack = _network_stack;
00190     } else {
00191         _observer.socket_error(M2MConnectionHandler::DNS_RESOLVING_ERROR, true);
00192         close_socket();
00193         return;
00194     }
00195 
00196     close_socket();
00197     init_socket();
00198 
00199     if(is_tcp_connection()) {
00200        tr_debug("M2MConnectionHandlerPimpl::resolve_server_address - Using TCP");
00201         if (((TCPSocket*)_socket)->connect(*_socket_address) < 0) {
00202             _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT);
00203             return;
00204         }
00205     }
00206 
00207     _running = true;
00208 
00209     if (_security) {
00210         if (_security->resource_value_int(M2MSecurity::SecurityMode) == M2MSecurity::Certificate ||
00211             _security->resource_value_int(M2MSecurity::SecurityMode) == M2MSecurity::Psk) {
00212 
00213             if( _security_impl != NULL ){
00214                 _security_impl->reset();
00215                 if (_security_impl->init(_security) == 0) {
00216                     _is_handshaking = true;
00217                     tr_debug("M2MConnectionHandlerPimpl::resolve_server_address - connect DTLS");
00218                     if(_security_impl->start_connecting_non_blocking(_base) < 0 ){
00219                         tr_debug("M2MConnectionHandlerPimpl::dns_handler - handshake failed");
00220                         _is_handshaking = false;
00221                         _observer.socket_error(M2MConnectionHandler::SSL_CONNECTION_ERROR);
00222                         close_socket();
00223                         return;
00224                     }
00225                 } else {
00226                     tr_error("M2MConnectionHandlerPimpl::resolve_server_address - init failed");
00227                     _observer.socket_error(M2MConnectionHandler::SSL_CONNECTION_ERROR, false);
00228                     close_socket();
00229                     return;
00230                 }
00231             } else {
00232                 tr_error("M2MConnectionHandlerPimpl::dns_handler - sec is null");
00233                 _observer.socket_error(M2MConnectionHandler::SSL_CONNECTION_ERROR, false);
00234                 close_socket();
00235                 return;
00236             }
00237         }
00238     }
00239     if(!_is_handshaking) {
00240         enable_keepalive();
00241         _observer.address_ready(_address,
00242                                 _server_type,
00243                                 _address._port);
00244     }
00245 }
00246 
00247 void M2MConnectionHandlerPimpl::send_handler()
00248 {
00249     tr_debug("M2MConnectionHandlerPimpl::send_handler()");
00250     _observer.data_sent();
00251 }
00252 
00253 bool M2MConnectionHandlerPimpl::send_data(uint8_t *data,
00254                                           uint16_t data_len,
00255                                           sn_nsdl_addr_s *address)
00256 {
00257     tr_debug("M2MConnectionHandlerPimpl::send_data()");
00258     if (address == NULL || data == NULL) {
00259         return false;
00260     }
00261 
00262     uint8_t *buffer = (uint8_t*)malloc(data_len);
00263     if(!buffer) {
00264         return false;
00265     }
00266 
00267     TaskIdentifier* task = memory_pool.alloc();
00268     if (!task) {
00269         free(buffer);
00270         return false;
00271     }
00272     task->pimpl = this;
00273     memcpy(buffer, data, data_len);
00274     task->data_ptr = buffer;
00275     arm_event_s event;
00276     event.receiver = M2MConnectionHandlerPimpl::_tasklet_id;
00277     event.sender = 0;
00278     event.event_type = ESocketSend;
00279     event.data_ptr = task;
00280     event.event_data = data_len;
00281     event.priority = ARM_LIB_HIGH_PRIORITY_EVENT;
00282 
00283     return eventOS_event_send(&event) == 0 ? true : false;
00284 }
00285 
00286 void M2MConnectionHandlerPimpl::send_socket_data(uint8_t *data,
00287                                                  uint16_t data_len)
00288 {
00289     bool success = false;
00290     if( _use_secure_connection ){
00291         if( _security_impl->send_message(data, data_len) > 0){
00292             success = true;
00293         }
00294     } else {
00295         int32_t ret = -1;
00296         if(is_tcp_connection()){
00297             //We need to "shim" the length in front
00298             uint16_t d_len = data_len+4;
00299             uint8_t* d = (uint8_t*)malloc(data_len+4);
00300 
00301             d[0] = (data_len >> 24 )& 0xff;
00302             d[1] = (data_len >> 16 )& 0xff;
00303             d[2] = (data_len >> 8 )& 0xff;
00304             d[3] = data_len & 0xff;
00305             memmove(d+4, data, data_len);
00306             ret = ((TCPSocket*)_socket)->send(d,d_len);
00307             free(d);
00308         }else {
00309             ret = ((UDPSocket*)_socket)->sendto(*_socket_address,data, data_len);
00310         }
00311         if (ret > 0) {
00312             success = true;
00313         }
00314     }
00315 
00316     if (!success) {
00317         _observer.socket_error(M2MConnectionHandler::SOCKET_SEND_ERROR, true);
00318         close_socket();
00319     }
00320 }
00321 
00322 int8_t M2MConnectionHandlerPimpl::connection_tasklet_handler()
00323 {
00324     return M2MConnectionHandlerPimpl::_tasklet_id;
00325 }
00326 
00327 void M2MConnectionHandlerPimpl::socket_event()
00328 {
00329     TaskIdentifier* task = memory_pool.alloc();
00330     if (!task) {
00331         _observer.socket_error(M2MConnectionHandler::SOCKET_READ_ERROR, true);
00332         return;
00333     }
00334     task->pimpl = this;
00335 
00336     arm_event_s event;
00337     event.receiver = M2MConnectionHandlerPimpl::_tasklet_id;
00338     event.sender = 0;
00339     event.event_type = ESocketReadytoRead;
00340     event.data_ptr = task;
00341     event.priority = ARM_LIB_HIGH_PRIORITY_EVENT;
00342     int8_t error = eventOS_event_send(&event);
00343     if(error != 0) {
00344         _observer.socket_error(M2MConnectionHandler::SOCKET_READ_ERROR, true);
00345     }
00346 }
00347 
00348 bool M2MConnectionHandlerPimpl::start_listening_for_data()
00349 {
00350     tr_debug("M2MConnectionHandlerPimpl::start_listening_for_data()");
00351     // Boolean return required for other platforms,
00352     // not needed in mbed OS Socket.
00353     _listening = true;
00354     _running = true;
00355     return _listening;
00356 }
00357 
00358 void M2MConnectionHandlerPimpl::stop_listening()
00359 {
00360     tr_debug("M2MConnectionHandlerPimpl::stop_listening()");
00361     _listening = false;
00362     if(_security_impl) {
00363         _security_impl->reset();
00364     }
00365 }
00366 
00367 int M2MConnectionHandlerPimpl::send_to_socket(const unsigned char *buf, size_t len)
00368 {
00369     tr_debug("M2MConnectionHandlerPimpl::send_to_socket len - %d", len);
00370     int size = -1;
00371     if(is_tcp_connection()) {
00372         size = ((TCPSocket*)_socket)->send(buf,len);
00373     } else {
00374         size = ((UDPSocket*)_socket)->sendto(*_socket_address,buf,len);
00375     }
00376     tr_debug("M2MConnectionHandlerPimpl::send_to_socket size - %d", size);
00377     if(NSAPI_ERROR_WOULD_BLOCK == size){
00378         if(_is_handshaking) {
00379             return M2MConnectionHandler::CONNECTION_ERROR_WANTS_WRITE;
00380         } else {
00381             return len;
00382         }
00383     }else if(size < 0){
00384         return -1;
00385     }else{
00386         if(!_is_handshaking) {
00387             _observer.data_sent();
00388         }
00389         return size;
00390     }
00391 }
00392 
00393 int M2MConnectionHandlerPimpl::receive_from_socket(unsigned char *buf, size_t len)
00394 {
00395     tr_debug("M2MConnectionHandlerPimpl::receive_from_socket");
00396     int recv = -1;
00397     if(is_tcp_connection()) {
00398         recv = ((TCPSocket*)_socket)->recv(buf, len);
00399     } else {
00400         recv = ((UDPSocket*)_socket)->recvfrom(NULL,buf, len);
00401     }
00402     tr_debug("M2MConnectionHandlerPimpl::receive_from_socket recv size %d", recv);
00403     if(NSAPI_ERROR_WOULD_BLOCK == recv){
00404         return M2MConnectionHandler::CONNECTION_ERROR_WANTS_READ;
00405     }else if(recv < 0){
00406         return -1;
00407     }else{
00408         return recv;
00409     }
00410 }
00411 
00412 void M2MConnectionHandlerPimpl::handle_connection_error(int error)
00413 {
00414     tr_debug("M2MConnectionHandlerPimpl::handle_connection_error");
00415     _observer.socket_error(error);
00416 }
00417 
00418 void M2MConnectionHandlerPimpl::set_platform_network_handler(void *handler)
00419 {
00420     tr_debug("M2MConnectionHandlerPimpl::set_platform_network_handler");
00421     _net_iface = (NetworkInterface*)handler;
00422 }
00423 
00424 void M2MConnectionHandlerPimpl::receive_handshake_handler()
00425 {
00426     tr_debug("M2MConnectionHandlerPimpl::receive_handshake_handler()");
00427     if( _is_handshaking ){
00428         int ret = _security_impl->continue_connecting();
00429         tr_debug("M2MConnectionHandlerPimpl::receive_handshake_handler() - ret %d", ret);
00430         if( ret == M2MConnectionHandler::CONNECTION_ERROR_WANTS_READ ){ //We wait for next readable event
00431             tr_debug("M2MConnectionHandlerPimpl::receive_handshake_handler() - We wait for next readable event");
00432             return;
00433         } else if( ret == 0 ){
00434             _is_handshaking = false;
00435             _use_secure_connection = true;
00436             enable_keepalive();
00437             _observer.address_ready(_address,
00438                                     _server_type,
00439                                     _server_port);
00440         }else if( ret < 0 ){
00441             _is_handshaking = false;
00442             _observer.socket_error(M2MConnectionHandler::SSL_CONNECTION_ERROR, true);
00443             close_socket();
00444         }
00445     }
00446 }
00447 
00448 bool M2MConnectionHandlerPimpl::is_handshake_ongoing()
00449 {
00450     return _is_handshaking;
00451 }
00452 
00453 void M2MConnectionHandlerPimpl::receive_handler()
00454 {
00455     tr_debug("M2MConnectionHandlerPimpl::receive_handler()");
00456     memset(_recv_buffer, 0, 1024);
00457     size_t receive_length = sizeof(_recv_buffer);
00458 
00459     if(_listening) {
00460         if( _use_secure_connection ){
00461             int rcv_size = _security_impl->read(_recv_buffer, receive_length);
00462 
00463             if(rcv_size >= 0){
00464                 _observer.data_available((uint8_t*)_recv_buffer,
00465                                          rcv_size, _address);
00466             } else if (M2MConnectionHandler::CONNECTION_ERROR_WANTS_READ != rcv_size) {
00467                 _observer.socket_error(M2MConnectionHandler::SOCKET_READ_ERROR, true);
00468                 close_socket();
00469                 return;
00470             }
00471         }else{
00472             int recv = -1;
00473             if(is_tcp_connection()){
00474                 recv = ((TCPSocket*)_socket)->recv(_recv_buffer, receive_length);
00475 
00476             }else{
00477                 recv = ((UDPSocket*)_socket)->recvfrom(NULL,_recv_buffer, receive_length);
00478             }
00479             if (recv > 0) {
00480                 // Send data for processing.
00481                 if(is_tcp_connection()){
00482                     //We need to "shim" out the length from the front
00483                     if( receive_length > 4 ){
00484                         uint64_t len = (_recv_buffer[0] << 24 & 0xFF000000) + (_recv_buffer[1] << 16 & 0xFF0000);
00485                         len += (_recv_buffer[2] << 8 & 0xFF00) + (_recv_buffer[3] & 0xFF);
00486                         if(len > 0) {
00487                             uint8_t* buf = (uint8_t*)malloc(len);
00488                             if(buf) {
00489                                 memmove(buf, _recv_buffer+4, len);
00490                                 // Observer for TCP plain mode
00491                                 _observer.data_available(buf,len,_address);
00492                                 free(buf);
00493                             }
00494                         }
00495                     }else{
00496                         _observer.socket_error(M2MConnectionHandler::SOCKET_READ_ERROR, true);
00497                         close_socket();
00498                     }
00499                 } else { // Observer for UDP plain mode
00500                     tr_debug("M2MConnectionHandlerPimpl::receive_handler - data received %d", recv);
00501                     _observer.data_available((uint8_t*)_recv_buffer,
00502                                              recv, _address);
00503                 }
00504             } else if(NSAPI_ERROR_WOULD_BLOCK != recv) {
00505                 // Socket error in receiving
00506                 _observer.socket_error(M2MConnectionHandler::SOCKET_READ_ERROR, true);
00507                 close_socket();
00508             }
00509         }
00510     }
00511 }
00512 
00513 void M2MConnectionHandlerPimpl::claim_mutex()
00514 {
00515     eventOS_scheduler_mutex_wait();
00516 }
00517 
00518 void M2MConnectionHandlerPimpl::release_mutex()
00519 {
00520     eventOS_scheduler_mutex_release();
00521 }
00522 
00523 void M2MConnectionHandlerPimpl::init_socket()
00524 {
00525     tr_debug("M2MConnectionHandlerPimpl::init_socket - IN");
00526     _is_handshaking = false;
00527     _running = true;
00528 
00529     if(is_tcp_connection()) {
00530        tr_debug("M2MConnectionHandlerPimpl::init_socket - Using TCP");
00531         _socket = new TCPSocket(_net_iface);
00532         if(_socket) {
00533             _socket->attach(this, &M2MConnectionHandlerPimpl::socket_event);
00534         } else {
00535             _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT);
00536             return;
00537         }
00538     } else {
00539        tr_debug("M2MConnectionHandlerPimpl::init_socket - Using UDP - port %d", _listen_port);
00540         _socket = new UDPSocket(_net_iface);
00541         if(_socket) {
00542             _socket->bind(_listen_port);
00543             _socket->attach(this, &M2MConnectionHandlerPimpl::socket_event);
00544         } else {
00545             _observer.socket_error(M2MConnectionHandler::SOCKET_ABORT);
00546             return;
00547         }
00548     }
00549     _socket->set_blocking(false);
00550     tr_debug("M2MConnectionHandlerPimpl::init_socket - OUT");
00551 }
00552 
00553 bool M2MConnectionHandlerPimpl::is_tcp_connection()
00554 {
00555     return _binding_mode == M2MInterface::TCP ||
00556             _binding_mode == M2MInterface::TCP_QUEUE ? true : false;
00557 }
00558 
00559 void M2MConnectionHandlerPimpl::close_socket()
00560 {
00561     tr_debug("M2MConnectionHandlerPimpl::close_socket() - IN");
00562     if(_socket) {
00563        _running = false;
00564         _socket->close();
00565         delete _socket;
00566         _socket = NULL;
00567     }
00568     tr_debug("M2MConnectionHandlerPimpl::close_socket() - OUT");
00569 }
00570 
00571 void M2MConnectionHandlerPimpl::enable_keepalive()
00572 {
00573 #if MBED_CLIENT_TCP_KEEPALIVE_TIME
00574     if(is_tcp_connection()) {
00575         int keepalive = MBED_CLIENT_TCP_KEEPALIVE_TIME;
00576         int enable = 1;
00577         tr_debug("M2MConnectionHandlerPimpl::resolve_hostname - keepalive %d s\n", keepalive);
00578         if(_socket->setsockopt(1,NSAPI_KEEPALIVE,&enable,sizeof(enable)) != 0) {
00579             tr_error("M2MConnectionHandlerPimpl::enable_keepalive - setsockopt fail to Set Keepalive\n");
00580         }
00581         if(_socket->setsockopt(1,NSAPI_KEEPINTVL,&keepalive,sizeof(keepalive)) != 0) {
00582             tr_error("M2MConnectionHandlerPimpl::enable_keepalive - setsockopt fail to Set Keepalive TimeInterval\n");
00583         }
00584         if(_socket->setsockopt(1,NSAPI_KEEPIDLE,&keepalive,sizeof(keepalive)) != 0) {
00585             tr_error("M2MConnectionHandlerPimpl::enable_keepalive - setsockopt fail to Set Keepalive Time\n");
00586         }
00587     }
00588 #endif
00589 }