Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: TYBLE16_simple_data_logger TYBLE16_MP3_Air
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
Generated on Tue Jul 12 2022 13:54:42 by
1.7.2