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.
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 }
Generated on Tue Jul 12 2022 12:58:27 by
1.7.2