Kenji Arai / mbed-os_TYBLE16

Dependents:   TYBLE16_simple_data_logger TYBLE16_MP3_Air

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers ppp_service.cpp Source File

ppp_service.cpp

00001 /*
00002  * Copyright (c) 2019 ARM Limited
00003  * SPDX-License-Identifier: Apache-2.0
00004  *
00005  * Licensed under the Apache License, Version 2.0 (the "License");
00006  * you may not use this file except in compliance with the License.
00007  * You may obtain a copy of the License at
00008  *
00009  *     http://www.apache.org/licenses/LICENSE-2.0
00010  *
00011  * Unless required by applicable law or agreed to in writing, software
00012  * distributed under the License is distributed on an "AS IS" BASIS,
00013  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00014  * See the License for the specific language governing permissions and
00015  * limitations under the License.
00016  */
00017 
00018 #include <stdlib.h>
00019 #include <stdio.h>
00020 #include "mbed_interface.h"
00021 #include "mbed_trace.h"
00022 #include "netsocket/nsapi_types.h"
00023 
00024 #if PPP_MBED_EVENT_QUEUE
00025 #include "mbed_shared_queues.h"
00026 #else
00027 #include "Thread.h"
00028 #include "EventQueue.h"
00029 #endif
00030 
00031 #include "mbed_poll.h"
00032 #include "ip4string.h"
00033 #include "ip6string.h"
00034 #include "ppp_service.h"
00035 
00036 #include "ppp_opts.h"
00037 #include "ppp_service_if.h"
00038 
00039 extern "C" { // "pppos.h" is missing extern C
00040 #include "pppos.h"
00041 #include "ppp_impl.h"
00042 }
00043 
00044 #define TRACE_GROUP "ppp_serv"
00045 
00046 #define PPP_SERVICE_IF_NAME "pp"
00047 
00048 #if PPP_SUPPORT
00049 
00050 #if PPP_DEBUG || PPP_TRACE_ENABLE
00051 // PPP internal traces enabled
00052 #define PPP_THREAD_STACKSIZE   MBED_CONF_PPP_THREAD_STACKSIZE * 3
00053 #elif MBED_DEBUG
00054 // Compiled with debug enabled
00055 #define PPP_THREAD_STACKSIZE   MBED_CONF_PPP_THREAD_STACKSIZE * 2
00056 #else
00057 #define PPP_THREAD_STACKSIZE   MBED_CONF_PPP_THREAD_STACKSIZE
00058 #endif
00059 
00060 /* Timeout to wait for PPP connection to be terminated
00061  * (LCP Terminate-Request is answered with Terminate-Ack)
00062  */
00063 #define PPP_TERMINATION_TIMEOUT           30000
00064 
00065 // If both IPCP and IPCP6 are made, how long to wait for both to complete
00066 #define PPP_IPCP_IPCP6_DELAY              5000
00067 
00068 bool ppp_service::prepare_event_queue()
00069 {
00070 #if PPP_MBED_EVENT_QUEUE
00071     ppp_service_event_queue = mbed::mbed_event_queue();
00072     if (!ppp_service_event_queue) {
00073         return false;
00074     }
00075 
00076     return true;
00077 #else
00078     static events::EventQueue *event_queue = NULL;
00079     static rtos::Thread *event_thread = NULL;
00080     // Already prepared
00081     if (event_queue && event_thread) {
00082         return true;
00083     }
00084 
00085     // Used for incoming data, timers, link status callback, power up callback
00086     event_queue = new events::EventQueue(10 * EVENTS_EVENT_SIZE, NULL);
00087     event_thread = new rtos::Thread(osPriorityNormal, PPP_THREAD_STACKSIZE, NULL, "ppp");
00088 
00089     if (event_thread->start(callback(event_queue, &events::EventQueue::dispatch_forever)) != osOK) {
00090         delete event_thread;
00091         delete event_queue;
00092         return false;
00093     }
00094 
00095     ppp_service_event_queue = event_queue;
00096 
00097     return true;
00098 #endif
00099 }
00100 
00101 uint32_t ppp_output(struct ppp_pcb_s *pcb, uint8_t *data, uint32_t len, void *ctx)
00102 {
00103 #if PPP_TRACE_ENABLE
00104     ppp_trace_to_ascii_hex_dump(OUTPUT_BUFFER, len, reinterpret_cast<char *>(data));
00105     ppp_trace_to_ascii_hex_dump_print(OUTPUT_BUFFER);
00106 #endif
00107 
00108     mbed::FileHandle *stream = (mbed::FileHandle *) pcb->netif->stream;
00109     if (!stream) {
00110         return 0;
00111     }
00112 
00113     mbed::pollfh fhs;
00114     fhs.fh = stream;
00115     fhs.events = POLLOUT;
00116 
00117     // stack expects us to block on write
00118     // File handle will be in non-blocking mode, because of read events.
00119     // Therefore must use poll to achieve the necessary block for writing.
00120 
00121     uint32_t written = 0;
00122 
00123     while (len != 0) {
00124         // Block forever until we're selected - don't care about reason we wake;
00125         // return from write should tell us what's up.
00126         poll(&fhs, 1, -1);
00127         // This write will be non-blocking, but blocking would be fine.
00128         ssize_t ret = stream->write(data, len);
00129         if (ret == -EAGAIN) {
00130             continue;
00131         } else if (ret < 0) {
00132             break;
00133         }
00134         written += ret;
00135         data += ret;
00136         len -= ret;
00137     }
00138 
00139     return written;
00140 }
00141 
00142 #if !PPP_INPROC_IRQ_SAFE
00143 #error "PPP_INPROC_IRQ_SAFE must be enabled"
00144 #endif
00145 void ppp_service::ppp_input()
00146 {
00147     // Allow new events from now, avoiding potential races around the read
00148     ppp_service_event_queued = false;
00149 
00150     if (!ppp_service_stream) {
00151         return;
00152     }
00153 
00154     // Non-blocking error check handler
00155     mbed::pollfh fhs;
00156     fhs.fh = ppp_service_stream;
00157     fhs.events = POLLIN;
00158     poll(&fhs, 1, 0);
00159     if (fhs.revents & (POLLHUP | POLLERR | POLLNVAL)) {
00160         ppp_handle_modem_hangup();
00161         return;
00162     }
00163 
00164     // Infinite loop, but we assume that we can read faster than the
00165     // serial, so we will fairly rapidly hit -EAGAIN.
00166     for (;;) {
00167         u8_t buffer[16];
00168 
00169         ssize_t len = ppp_service_stream->read(buffer, sizeof buffer);
00170 
00171         if (len == -EAGAIN) {
00172             break;
00173         } else if (len <= 0) {
00174             ppp_handle_modem_hangup();
00175             return;
00176         }
00177 
00178 #if PPP_TRACE_ENABLE
00179         ppp_trace_to_ascii_hex_dump(INPUT_BUFFER, len, reinterpret_cast<char *>(buffer));
00180 #endif
00181 
00182         pppos_input(static_cast<ppp_pcb *>(ppp_service_pcb), buffer, len);
00183     }
00184 }
00185 
00186 void ppp_service::ppp_stream_sigio_callback()
00187 {
00188     if (ppp_service_stream && !ppp_service_event_queued) {
00189         ppp_service_event_queued = true;
00190         if (ppp_service_event_queue->call(mbed::callback(this, &ppp_service::ppp_input)) == 0) {
00191             ppp_service_event_queued = false;
00192         }
00193     }
00194 }
00195 
00196 void ppp_service::ppp_handle_modem_hangup()
00197 {
00198     ppp_pcb *pcb = static_cast<ppp_pcb *>(ppp_service_pcb);
00199 
00200     if (pcb->phase != PPP_PHASE_DEAD) {
00201         ppp_close(pcb, 1);
00202     }
00203 }
00204 
00205 void ppp_service::ppp_link_status(struct ppp_pcb_s *pcb, int err_code, void *ctx)
00206 {
00207     ppp_service *ppp_service_ptr = static_cast<ppp_service *>(pcb->netif->service_ptr);
00208 
00209     switch (err_code) {
00210         case PPPERR_NONE:
00211 #if PPP_DEBUG
00212             PPPDEBUG(LOG_DEBUG, ("status_cb: Connected"));
00213             char address[40];
00214 #if PPP_IPV4_SUPPORT
00215             ip4tos(pcb->netif->ipv4_addr.bytes, address);
00216             PPPDEBUG(LOG_DEBUG, ("   our_ipaddr  = %s", address));
00217             ip4tos(pcb->netif->ipv4_gateway.bytes, address);
00218             PPPDEBUG(LOG_DEBUG, ("   his_ipaddr  = %s", address));
00219             ip4tos(pcb->netif->ipv4_netmask.bytes, address);
00220             PPPDEBUG(LOG_DEBUG, ("   netmask     = %s", address));
00221             if (pcb->netif->ipv4_dns_server[0].version == NSAPI_IPv4 ) {
00222                 ip4tos(pcb->netif->ipv4_dns_server[0].bytes, address);
00223                 PPPDEBUG(LOG_DEBUG, ("   dns1        = %s", address));
00224             }
00225             if (pcb->netif->ipv4_dns_server[1].version == NSAPI_IPv4 ) {
00226                 ip4tos(pcb->netif->ipv4_dns_server[1].bytes, address);
00227                 PPPDEBUG(LOG_DEBUG, ("   dns2        = %s", address));
00228             }
00229 #endif /* PPP_IPV4_SUPPORT */
00230 #if PPP_IPV6_SUPPORT
00231             ip6tos(pcb->netif->ipv6_addr.bytes, address);
00232             PPPDEBUG(LOG_DEBUG, ("   our6_ipaddr = %s", address));
00233 #endif /* PPP_IPV6_SUPPORT */
00234 #endif
00235             break;
00236 
00237         case PPPERR_PARAM:
00238             PPPDEBUG(LOG_DEBUG, ("status_cb: Invalid parameter"));
00239             break;
00240 
00241         case PPPERR_OPEN:
00242             PPPDEBUG(LOG_DEBUG, ("status_cb: Unable to open PPP session"));
00243             break;
00244 
00245         case PPPERR_DEVICE:
00246             PPPDEBUG(LOG_DEBUG, ("status_cb: Invalid I/O device for PPP"));
00247             break;
00248 
00249         case PPPERR_ALLOC:
00250             PPPDEBUG(LOG_DEBUG, ("status_cb: Unable to allocate resources"));
00251             break;
00252 
00253         case PPPERR_USER:
00254             PPPDEBUG(LOG_DEBUG, ("status_cb: User interrupt"));
00255             break;
00256 
00257         case PPPERR_CONNECT:
00258             PPPDEBUG(LOG_DEBUG, ("status_cb: Connection lost"));
00259             break;
00260 
00261         case PPPERR_AUTHFAIL:
00262             PPPDEBUG(LOG_DEBUG, ("status_cb: Failed authentication challenge"));
00263             break;
00264 
00265         case PPPERR_PROTOCOL:
00266             PPPDEBUG(LOG_DEBUG, ("status_cb: Failed to meet protocol"));
00267             break;
00268 
00269         case PPPERR_PEERDEAD:
00270             PPPDEBUG(LOG_DEBUG, ("status_cb: Connection timeout"));
00271             break;
00272 
00273         case PPPERR_IDLETIMEOUT:
00274             PPPDEBUG(LOG_DEBUG, ("status_cb: Idle Timeout"));
00275             break;
00276 
00277         case PPPERR_CONNECTTIME:
00278             PPPDEBUG(LOG_DEBUG, ("status_cb: Max connect time reached"));
00279             break;
00280 
00281         case PPPERR_LOOPBACK:
00282             PPPDEBUG(LOG_DEBUG, ("status_cb: Loopback detected"));
00283             break;
00284 
00285         default:
00286             PPPDEBUG(LOG_DEBUG, ("status_cb: Unknown error code %d", err_code));
00287             break;
00288     }
00289 
00290     if (err_code == PPPERR_NONE) {
00291         return;
00292     }
00293 
00294     /* If some error happened, we need to properly shutdown the PPP interface  */
00295     if (ppp_service_ptr->ppp_service_active) {
00296         ppp_service_ptr->ppp_service_active = false;
00297         ppp_service_ptr->ppp_service_close_sem.release();
00298     }
00299 }
00300 
00301 nsapi_error_t ppp_service::ppp_stack_if_init()
00302 {
00303     ppp_init();
00304 
00305     if (!ppp_service_pcb) {
00306         ppp_service_pcb = pppos_create(static_cast<netif *>(ppp_service_netif),
00307                                        ppp_output, ppp_link_status, NULL);
00308         if (!ppp_service_pcb) {
00309             return NSAPI_ERROR_DEVICE_ERROR ;
00310         }
00311     }
00312 
00313 #if PPP_IPV4_SUPPORT
00314     ppp_pcb *pcb = static_cast<ppp_pcb *>(ppp_service_pcb);
00315 
00316 #if PPP_IPV6_SUPPORT
00317     if (ppp_service_stack != IPV6_STACK) {
00318         ppp_set_usepeerdns(pcb, true);
00319     }
00320 #else
00321     ppp_set_usepeerdns(pcb, true);
00322 #endif
00323 #endif
00324 
00325 #if PPP_IPV4_SUPPORT && PPP_IPV6_SUPPORT
00326     if (ppp_service_stack == IPV4_STACK) {
00327         pcb->ipv6cp_disabled = true;
00328     } else if (ppp_service_stack == IPV6_STACK) {
00329         pcb->ipcp_disabled = true;
00330     }
00331 #endif
00332     return NSAPI_ERROR_OK ;
00333 }
00334 
00335 nsapi_error_t ppp_service::ppp_if_connect()
00336 {
00337     ppp_pcb *pcb = static_cast<ppp_pcb *>(ppp_service_pcb);
00338 
00339 #if PPP_AUTH_SUPPORT
00340     ppp_set_auth(pcb, PPPAUTHTYPE_ANY, ppp_service_uname, ppp_service_password);
00341 #endif //PPP_AUTH_SUPPORT
00342     ppp_service_active = true;
00343     ppp_service_terminating = false;
00344     err_t ret = ppp_connect(pcb, 0);
00345     // input must not be called until after connect
00346     if (ret == ERR_OK) {
00347         ppp_service_stream->sigio(mbed::callback(this, &ppp_service::ppp_stream_sigio_callback));
00348     } else {
00349         ppp_service_active = false;
00350     }
00351 
00352     return ret;
00353 }
00354 
00355 nsapi_error_t ppp_service::ppp_if_disconnect()
00356 {
00357     ppp_pcb *pcb = static_cast<ppp_pcb *>(ppp_service_pcb);
00358 
00359     err_t ret = ERR_OK;
00360     if (ppp_service_active) {
00361         ppp_service_terminating = true;
00362         ret = ppp_close(pcb, 0);
00363         if (ret == ERR_OK) {
00364             /* close call made, now let's catch the response in the status callback */
00365             ppp_service_close_sem.try_acquire_for(PPP_TERMINATION_TIMEOUT);
00366         }
00367         ppp_service_active = false;
00368     }
00369     return ret;
00370 }
00371 
00372 ppp_service::ppp_service()
00373 {
00374     ppp_service_stream = NULL;
00375     ppp_service_event_queue = NULL;
00376 
00377     ppp_service_netif = static_cast<netif *>(malloc(sizeof(netif)));
00378     if (ppp_service_netif) {
00379         memset(ppp_service_netif, 0, sizeof(netif));
00380         ppp_service_netif->service_ptr = static_cast<void *>(this);
00381     }
00382 
00383     ppp_service_pcb = NULL;
00384     ppp_service_stack = IPV4_STACK;
00385     ppp_service_uname = NULL;
00386     ppp_service_password = NULL;
00387     memory_manager = NULL;
00388     ppp_service_active = false;
00389     ppp_service_event_queued = false;
00390     ppp_service_terminating = false;
00391     ppp_link_state_up = false;
00392 }
00393 
00394 bool ppp_service::link_out(net_stack_mem_buf_t *buf, nsapi_ip_stack_t ip_stack)
00395 {
00396     netif *serv_netif = static_cast<netif *>(ppp_service_netif);
00397 
00398     if (ppp_service_terminating) {
00399         memory_manager->free(buf);
00400         return true;
00401     }
00402 
00403     struct pbuf *p = static_cast<struct pbuf *>(ppp_memory_buffer_convert_to(memory_manager, buf));
00404     if (!p) {
00405         memory_manager->free(buf);
00406         return true;
00407     }
00408 
00409 #if PPP_IPV4_SUPPORT && PPP_IPV6_SUPPORT
00410     if (ip_stack == IPV4_STACK) {
00411         serv_netif->output(serv_netif, p, NULL);
00412     } else {
00413         serv_netif->output_ip6(serv_netif, p, NULL);
00414     }
00415 #elif PPP_IPV4_SUPPORT
00416     serv_netif->output(serv_netif, p, NULL);
00417 #elif PPP_IPV6_SUPPORT
00418     serv_netif->output_ip6(serv_netif, p, NULL);
00419 #endif
00420 
00421     ppp_memory_buffer_free(p); // Not done on PPP lower layers
00422     return true;
00423 }
00424 
00425 bool ppp_service::power_up()
00426 {
00427     if (!ppp_service_netif || !ppp_service_stream) {
00428         return false;
00429     }
00430 
00431     if (!prepare_event_queue()) {
00432         return false;
00433     }
00434 
00435     if (ppp_service_event_queue->call(mbed::callback(this, &ppp_service::power_up_call)) == 0) {
00436         return false;
00437     }
00438 
00439     return true;
00440 }
00441 
00442 void ppp_service::power_up_call()
00443 {
00444     nsapi_error_t err = ppp_stack_if_init();
00445 
00446     if (err != NSAPI_ERROR_OK ) {
00447         return;
00448     }
00449 
00450     err = ppp_if_connect();
00451 
00452     if (err != NSAPI_ERROR_OK ) {
00453         return;
00454     }
00455 }
00456 
00457 uint32_t ppp_service::get_mtu_size()
00458 {
00459     if (!ppp_service_netif) {
00460         return 0;
00461     }
00462 
00463     netif *serv_netif = static_cast<netif *>(ppp_service_netif);
00464     return serv_netif->mtu;
00465 }
00466 
00467 uint32_t ppp_service::get_align_preference() const
00468 {
00469     return 0;
00470 }
00471 
00472 void ppp_service::get_ifname(char *name, uint8_t size) const
00473 {
00474     memcpy(name, PPP_SERVICE_IF_NAME, (size < sizeof(PPP_SERVICE_IF_NAME)) ? size : sizeof(PPP_SERVICE_IF_NAME));
00475 }
00476 
00477 void ppp_service::set_link_input_cb(ppp_link_input_cb_t input_cb)
00478 {
00479     ppp_link_input_cb = input_cb;
00480 }
00481 
00482 void ppp_service::set_link_state_cb(ppp_link_state_change_cb_t state_cb)
00483 {
00484     ppp_link_state_cb = state_cb;
00485 }
00486 
00487 void ppp_service::power_down()
00488 {
00489     ppp_if_disconnect();
00490 }
00491 
00492 void ppp_service::set_memory_manager(NetStackMemoryManager &mem_mngr)
00493 {
00494     memory_manager = &mem_mngr;
00495 
00496     if (!ppp_service_netif) {
00497         return;
00498     }
00499     ppp_service_netif->memory_manager = &mem_mngr;
00500 }
00501 
00502 void ppp_service::set_stream(mbed::FileHandle *stream)
00503 {
00504     ppp_service_stream = stream;
00505     if (!ppp_service_netif) {
00506         return;
00507     }
00508     ppp_service_netif->stream = stream;
00509 }
00510 
00511 void ppp_service::set_ip_stack(nsapi_ip_stack_t ip_stack)
00512 {
00513     ppp_service_stack = ip_stack;
00514 }
00515 
00516 void ppp_service::set_credentials(const char *uname, const char *password)
00517 {
00518     if (strlen(uname) > 0) {
00519         ppp_service_uname = uname;
00520     } else {
00521         ppp_service_uname = NULL;
00522     }
00523     if (strlen(password) > 0) {
00524         ppp_service_password = password;
00525     } else {
00526         password = NULL;
00527     }
00528 }
00529 
00530 const nsapi_addr_t *ppp_service::get_ip_address(nsapi_version_t version)
00531 {
00532 #if PPP_IPV6_SUPPORT || PPP_IPV4_SUPPORT
00533     netif *serv_netif = static_cast<netif *>(ppp_service_netif);
00534 #endif
00535 
00536 #if PPP_IPV6_SUPPORT
00537     if (version == NSAPI_IPv6  && serv_netif->ipv6_addr.version == NSAPI_IPv6 ) {
00538         return &serv_netif->ipv6_addr;
00539     }
00540 #endif
00541 
00542 #if PPP_IPV4_SUPPORT
00543     if (version == NSAPI_IPv4  && serv_netif->ipv4_addr.version == NSAPI_IPv4 ) {
00544         return &serv_netif->ipv4_addr;
00545     }
00546 #endif
00547 
00548     return NULL;
00549 }
00550 
00551 const nsapi_addr_t *ppp_service::get_netmask()
00552 {
00553 #if PPP_IPV4_SUPPORT
00554     if (ppp_service_netif->ipv4_netmask.version == NSAPI_IPv4 ) {
00555         return &ppp_service_netif->ipv4_netmask;
00556     }
00557 #endif
00558 
00559     return NULL;
00560 }
00561 
00562 const nsapi_addr_t *ppp_service::get_gateway()
00563 {
00564 #if PPP_IPV4_SUPPORT
00565     if (ppp_service_netif->ipv4_gateway.version == NSAPI_IPv4 ) {
00566         return &ppp_service_netif->ipv4_gateway;
00567     }
00568 #endif
00569 
00570     return NULL;
00571 }
00572 
00573 const nsapi_addr_t *ppp_service::get_dns_server(uint8_t index)
00574 {
00575 #if PPP_IPV4_SUPPORT
00576     if (index > 1) {
00577         return NULL;
00578     }
00579     if (ppp_service_netif->ipv4_dns_server[index].version == NSAPI_IPv4 ) {
00580         return &ppp_service_netif->ipv4_dns_server[index];
00581     }
00582 #endif
00583 
00584     return NULL;
00585 }
00586 
00587 void ppp_service::link_state(bool up)
00588 {
00589     if (!ppp_service_active) {
00590         return;
00591     }
00592 
00593     bool call_link_state = false;
00594 #if PPP_IPV4_SUPPORT && PPP_IPV6_SUPPORT
00595     bool callin_link_state = false;
00596     if (ppp_service_stack == IPV4V6_STACK) {
00597         if (up) {
00598             if (ppp_service_netif->ipv4_up && ppp_service_netif->ipv6_up) {
00599                 call_link_state = true;
00600             } else {
00601                 callin_link_state = true;
00602             }
00603         } else {
00604             call_link_state = true;
00605         }
00606     } else {
00607         call_link_state = true;
00608     }
00609 #else
00610     call_link_state = true;
00611 #endif
00612 
00613     if (call_link_state) {
00614         if (ppp_service_event_queue->call(mbed::callback(this, &ppp_service::link_state_call), up) == 0) {
00615             return;
00616         }
00617     }
00618 #if PPP_IPV4_SUPPORT && PPP_IPV6_SUPPORT
00619     if (callin_link_state) {
00620         if (ppp_service_event_queue->call_in(PPP_IPCP_IPCP6_DELAY, mbed::callback(this, &ppp_service::link_state_call), up) == 0) {
00621             return;
00622         }
00623     }
00624 #endif
00625 }
00626 
00627 void ppp_service::link_state_call(bool up)
00628 {
00629     if (ppp_link_state_up != up) {
00630         ppp_link_state_up = up;
00631         ppp_link_state_cb(up);
00632     }
00633 }
00634 
00635 void ppp_service::link_input(net_stack_mem_buf_t *buf)
00636 {
00637     if (ppp_service_terminating) {
00638         memory_manager->free(buf);
00639         return;
00640     }
00641 
00642     ppp_link_input_cb(buf);
00643 }
00644 
00645 events::EventQueue *ppp_service::event_queue_get()
00646 {
00647     return ppp_service_event_queue;
00648 }
00649 
00650 void ppp_service::resource_lock()
00651 {
00652     ppp_service_mutex.lock();
00653 }
00654 
00655 void ppp_service::resource_unlock()
00656 {
00657     ppp_service_mutex.unlock();
00658 }
00659 
00660 ppp_service &ppp_service::get_instance()
00661 {
00662     static ppp_service ppp_service_instance;
00663     return ppp_service_instance;
00664 }
00665 
00666 // Weak so a module can override
00667 MBED_WEAK PPP &PPP::get_default_instance()
00668 {
00669     return ppp_service::get_instance();
00670 }
00671 
00672 #endif
00673 
00674 /**
00675  * @}
00676  */
00677 
00678 /* --------------------------------- End Of File ------------------------------ */
00679