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.
coap_message_handler.c
00001 /* 00002 * Copyright (c) 2015-2017, Arm Limited and affiliates. 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 <string.h> 00019 #include "nsdynmemLIB.h" 00020 #include "coap_service_api_internal.h" 00021 #include "coap_message_handler.h" 00022 #include "mbed-coap/sn_coap_protocol.h" 00023 #include "source/include/sn_coap_protocol_internal.h" 00024 #include "socket_api.h" 00025 #include "ns_types.h" 00026 #include "ns_list.h" 00027 #include "ns_trace.h" 00028 #include "randLIB.h" 00029 00030 #define TRACE_GROUP "CoSA" 00031 00032 static void *own_alloc(uint16_t size) 00033 { 00034 if (size) { 00035 return ns_dyn_mem_temporary_alloc(size); 00036 } else { 00037 return 0; 00038 } 00039 } 00040 00041 static void own_free(void *ptr) 00042 { 00043 if (ptr) { 00044 ns_dyn_mem_free(ptr); 00045 } 00046 } 00047 00048 static NS_LIST_DEFINE(request_list, coap_transaction_t, link); 00049 00050 static coap_transaction_t *transaction_find_client_by_token(uint8_t *token, uint8_t token_len, const uint8_t address[static 16], uint16_t port) 00051 { 00052 (void) address; 00053 (void) port; 00054 coap_transaction_t *this = NULL; 00055 00056 ns_list_foreach(coap_transaction_t, cur_ptr, &request_list) { 00057 if ((cur_ptr->token_len == token_len) && (memcmp(cur_ptr->token, token, token_len) == 0) && cur_ptr->client_request) { 00058 this = cur_ptr; 00059 break; 00060 } 00061 } 00062 return this; 00063 } 00064 00065 static coap_transaction_t *transaction_find_server(uint16_t msg_id) 00066 { 00067 coap_transaction_t *this = NULL; 00068 ns_list_foreach(coap_transaction_t, cur_ptr, &request_list) { 00069 if (cur_ptr->msg_id == msg_id && !cur_ptr->client_request) { 00070 this = cur_ptr; 00071 break; 00072 } 00073 } 00074 return this; 00075 } 00076 00077 static coap_transaction_t *transaction_find_client(uint16_t msg_id) 00078 { 00079 coap_transaction_t *this = NULL; 00080 ns_list_foreach(coap_transaction_t, cur_ptr, &request_list) { 00081 if (cur_ptr->msg_id == msg_id && cur_ptr->client_request) { 00082 this = cur_ptr; 00083 break; 00084 } 00085 } 00086 return this; 00087 } 00088 00089 static coap_transaction_t *transaction_find_by_address(uint8_t *address_ptr, uint16_t port) 00090 { 00091 coap_transaction_t *this = NULL; 00092 ns_list_foreach(coap_transaction_t, cur_ptr, &request_list) { 00093 if (cur_ptr->remote_port == port && memcmp(cur_ptr->remote_address, address_ptr, 16) == 0) { 00094 this = cur_ptr; 00095 break; 00096 } 00097 } 00098 return this; 00099 } 00100 00101 /* retransmission valid time is calculated to be max. time that CoAP message sending can take: */ 00102 /* Number of retransmisisons, each retransmission is 2 * previous retransmisison time */ 00103 /* + random factor (max. 1.5) */ 00104 static uint32_t transaction_valid_time_calculate(void) 00105 { 00106 int i; 00107 uint32_t time_valid = 0; 00108 00109 for (i = 0; i <= COAP_RESENDING_COUNT; i++) { 00110 time_valid += (COAP_RESENDING_INTERVAL << i) * 1.5; 00111 } 00112 00113 return time_valid + coap_service_get_internal_timer_ticks(); 00114 } 00115 00116 static coap_transaction_t *transaction_create(void) 00117 { 00118 coap_transaction_t *this = ns_dyn_mem_alloc(sizeof(coap_transaction_t)); 00119 if (this) { 00120 memset(this, 0, sizeof(coap_transaction_t)); 00121 this->client_request = true;// default to client initiated method 00122 this->valid_until = transaction_valid_time_calculate(); 00123 ns_list_add_to_start(&request_list, this); 00124 } 00125 00126 return this; 00127 } 00128 00129 static void transaction_free(coap_transaction_t *this) 00130 { 00131 if (this->data_ptr) { 00132 ns_dyn_mem_free(this->data_ptr); 00133 } 00134 ns_dyn_mem_free(this); 00135 } 00136 00137 void transaction_delete(coap_transaction_t *this) 00138 { 00139 if (!coap_message_handler_transaction_valid(this)) { 00140 return; 00141 } 00142 ns_list_remove(&request_list, this); 00143 transaction_free(this); 00144 00145 return; 00146 } 00147 00148 void transactions_delete_all(uint8_t *address_ptr, uint16_t port) 00149 { 00150 coap_transaction_t *transaction = transaction_find_by_address(address_ptr, port); 00151 00152 while (transaction) { 00153 ns_list_remove(&request_list, transaction); 00154 if (transaction->resp_cb) { 00155 transaction->resp_cb(transaction->service_id, address_ptr, port, NULL); 00156 } 00157 sn_coap_protocol_delete_retransmission(coap_service_handle->coap, transaction->msg_id); 00158 transaction_free(transaction); 00159 transaction = transaction_find_by_address(address_ptr, port); 00160 } 00161 } 00162 00163 static int8_t coap_rx_function(sn_coap_hdr_s *resp_ptr, sn_nsdl_addr_s *address_ptr, void *param) 00164 { 00165 coap_transaction_t *this = NULL; 00166 (void)param; 00167 00168 if (resp_ptr->coap_status == COAP_STATUS_BUILDER_BLOCK_SENDING_DONE) { 00169 return 0; 00170 } 00171 00172 tr_warn("transaction was not handled %d", resp_ptr->msg_id); 00173 if (!resp_ptr || !address_ptr) { 00174 return -1; 00175 } 00176 if(resp_ptr->token_ptr){ 00177 this = transaction_find_client_by_token(resp_ptr->token_ptr, resp_ptr->token_len, address_ptr->addr_ptr, address_ptr->port); 00178 } 00179 if (!this) { 00180 return 0; 00181 } 00182 00183 ns_list_remove(&request_list, this); 00184 if (this->resp_cb) { 00185 this->resp_cb(this->service_id, address_ptr->addr_ptr, address_ptr->port, NULL); 00186 } 00187 transaction_free(this); 00188 return 0; 00189 } 00190 00191 coap_msg_handler_t *coap_message_handler_init(void *(*used_malloc_func_ptr)(uint16_t), void (*used_free_func_ptr)(void *), 00192 uint8_t (*used_tx_callback_ptr)(uint8_t *, uint16_t, sn_nsdl_addr_s *, void *)){ 00193 00194 if ((used_malloc_func_ptr == NULL) || (used_free_func_ptr == NULL) || (used_tx_callback_ptr == NULL)) { 00195 return NULL; 00196 } 00197 00198 coap_msg_handler_t *handle; 00199 handle = ns_dyn_mem_alloc(sizeof(coap_msg_handler_t)); 00200 if (handle == NULL) { 00201 return NULL; 00202 } 00203 00204 memset(handle, 0, sizeof(coap_msg_handler_t)); 00205 00206 handle->sn_coap_tx_callback = used_tx_callback_ptr; 00207 00208 handle->sn_coap_service_free = used_free_func_ptr; 00209 handle->sn_coap_service_malloc = used_malloc_func_ptr; 00210 00211 handle->coap = sn_coap_protocol_init(used_malloc_func_ptr, used_free_func_ptr, used_tx_callback_ptr, &coap_rx_function); 00212 if( !handle->coap ){ 00213 ns_dyn_mem_free(handle); 00214 return NULL; 00215 } 00216 00217 /* Set default buffer size for CoAP duplicate message detection */ 00218 sn_coap_protocol_set_duplicate_buffer_size(handle->coap, DUPLICATE_MESSAGE_BUFFER_SIZE); 00219 00220 /* Set default blockwise message size. */ 00221 sn_coap_protocol_set_block_size(handle->coap, DEFAULT_BLOCKWISE_DATA_SIZE); 00222 00223 /* Set default CoAP retransmission paramters */ 00224 sn_coap_protocol_set_retransmission_parameters(handle->coap, COAP_RESENDING_COUNT, COAP_RESENDING_INTERVAL); 00225 00226 return handle; 00227 } 00228 00229 int8_t coap_message_handler_destroy(coap_msg_handler_t *handle){ 00230 if( !handle ){ 00231 return -1; 00232 } 00233 00234 if( handle->coap ){ 00235 sn_coap_protocol_destroy(handle->coap); 00236 } 00237 00238 //Destroy transactions 00239 ns_list_foreach_safe(coap_transaction_t, cur_ptr, &request_list) { 00240 ns_list_remove(&request_list, cur_ptr); 00241 ns_dyn_mem_free(cur_ptr); 00242 cur_ptr = NULL; 00243 } 00244 00245 handle->sn_coap_service_free(handle); 00246 return 0; 00247 } 00248 00249 coap_transaction_t *coap_message_handler_transaction_valid(coap_transaction_t *tr_ptr) 00250 { 00251 ns_list_foreach(coap_transaction_t, cur_ptr, &request_list) { 00252 if (cur_ptr == tr_ptr) { 00253 return tr_ptr; 00254 } 00255 } 00256 return NULL; 00257 } 00258 00259 coap_transaction_t *coap_message_handler_find_transaction(uint8_t *address_ptr, uint16_t port) 00260 { 00261 if( !address_ptr ) 00262 return NULL; 00263 return transaction_find_by_address( address_ptr, port ); 00264 } 00265 00266 int16_t coap_message_handler_coap_msg_process(coap_msg_handler_t *handle, int8_t socket_id, const uint8_t source_addr_ptr[static 16], uint16_t port, const uint8_t dst_addr_ptr[static 16], 00267 uint8_t *data_ptr, uint16_t data_len, int16_t (cb)(int8_t, sn_coap_hdr_s *, coap_transaction_t *)) 00268 { 00269 sn_nsdl_addr_s src_addr; 00270 sn_coap_hdr_s *coap_message; 00271 int16_t ret_val = 0; 00272 coap_transaction_t *this = NULL; 00273 00274 if (!cb || !handle) { 00275 return -1; 00276 } 00277 00278 src_addr.addr_ptr = (uint8_t *)source_addr_ptr; 00279 src_addr.addr_len = 16; 00280 src_addr.type = SN_NSDL_ADDRESS_TYPE_IPV6; 00281 src_addr.port = port; 00282 00283 coap_transaction_t *transaction_ptr = transaction_create(); 00284 if (!transaction_ptr) { 00285 return -1; 00286 } 00287 transaction_ptr->service_id = coap_service_id_find_by_socket(socket_id); 00288 transaction_ptr->client_request = false;// this is server transaction 00289 memcpy(transaction_ptr->local_address, *(dst_addr_ptr) == 0xFF ? ns_in6addr_any : dst_addr_ptr, 16); 00290 memcpy(transaction_ptr->remote_address, source_addr_ptr, 16); 00291 transaction_ptr->remote_port = port; 00292 00293 coap_message = sn_coap_protocol_parse(handle->coap, &src_addr, data_len, data_ptr, transaction_ptr); 00294 if (coap_message == NULL) { 00295 transaction_delete(transaction_ptr); 00296 tr_err("CoAP Parsing failed"); 00297 return -1; 00298 } 00299 00300 tr_debug("CoAP status:%d, type:%d, code:%d, id:%d", coap_message->coap_status, coap_message->msg_type, coap_message->msg_code, coap_message->msg_id); 00301 00302 /* Check, if coap itself sends response, or block receiving is ongoing... */ 00303 if (coap_message->coap_status != COAP_STATUS_OK && coap_message->coap_status != COAP_STATUS_PARSER_BLOCKWISE_MSG_RECEIVED) { 00304 tr_debug("CoAP library responds"); 00305 transaction_delete(transaction_ptr); 00306 ret_val = -1; 00307 goto exit; 00308 } 00309 00310 /* Request received */ 00311 if (coap_message->msg_code > 0 && coap_message->msg_code < 32) { 00312 transaction_ptr->msg_id = coap_message->msg_id; 00313 transaction_ptr->req_msg_type = coap_message->msg_type; 00314 if (coap_message->token_len) { 00315 memcpy(transaction_ptr->token, coap_message->token_ptr, coap_message->token_len); 00316 transaction_ptr->token_len = coap_message->token_len; 00317 } 00318 if (cb(socket_id, coap_message, transaction_ptr) < 0) { 00319 // negative return value = message ignored -> delete transaction 00320 transaction_delete(transaction_ptr); 00321 } 00322 goto exit; 00323 /* Response received */ 00324 } else { 00325 if (coap_message->token_ptr) { 00326 this = transaction_find_client_by_token(coap_message->token_ptr, coap_message->token_len, source_addr_ptr, port); 00327 } 00328 if (!this) { 00329 tr_error("client transaction not found"); 00330 ret_val = -1; 00331 goto exit; 00332 } 00333 tr_debug("Service %d, response received", this->service_id); 00334 ns_list_remove(&request_list, this); 00335 if (this->resp_cb) { 00336 this->resp_cb(this->service_id, (uint8_t *)source_addr_ptr, port, coap_message); 00337 } 00338 transaction_free(this); 00339 } 00340 00341 exit: 00342 if (coap_message->coap_status == COAP_STATUS_PARSER_BLOCKWISE_MSG_RECEIVED) { 00343 handle->sn_coap_service_free(coap_message->payload_ptr); 00344 } 00345 00346 sn_coap_parser_release_allocated_coap_msg_mem(handle->coap, coap_message); 00347 00348 return ret_val; 00349 } 00350 00351 uint16_t coap_message_handler_request_send(coap_msg_handler_t *handle, int8_t service_id, uint8_t options, const uint8_t destination_addr[static 16], 00352 uint16_t destination_port, sn_coap_msg_type_e msg_type, sn_coap_msg_code_e msg_code, const char *uri, 00353 sn_coap_content_format_e cont_type, const uint8_t *payload_ptr, uint16_t payload_len, coap_message_handler_response_recv *request_response_cb) 00354 { 00355 coap_transaction_t *transaction_ptr; 00356 sn_coap_hdr_s request; 00357 sn_nsdl_addr_s dst_addr; 00358 uint8_t token[4]; 00359 uint16_t data_len; 00360 uint8_t *data_ptr; 00361 00362 tr_debug("Service %d, send CoAP request payload_len %d", service_id, payload_len); 00363 transaction_ptr = transaction_create(); 00364 00365 if (!uri || !transaction_ptr || !handle) { 00366 return 0; 00367 } 00368 00369 transaction_ptr->service_id = service_id; 00370 transaction_ptr->client_request = true; 00371 transaction_ptr->resp_cb = request_response_cb; 00372 transaction_ptr->options = options; 00373 memcpy(transaction_ptr->remote_address, destination_addr, 16); 00374 transaction_ptr->remote_port = destination_port; 00375 transaction_ptr->req_msg_type = msg_type; 00376 memset(&request, 0, sizeof(request)); 00377 dst_addr.addr_ptr = (uint8_t *) destination_addr; // Cast away const and trust that nsdl doesn't modify... 00378 dst_addr.addr_len = 16; 00379 dst_addr.type = SN_NSDL_ADDRESS_TYPE_IPV6; 00380 dst_addr.port = destination_port; 00381 00382 request.msg_type = msg_type; 00383 request.msg_code = msg_code; 00384 request.uri_path_ptr = (uint8_t *)uri; 00385 request.uri_path_len = strlen(uri); 00386 request.content_format = cont_type; 00387 00388 do{ 00389 randLIB_get_n_bytes_random(token,4); 00390 }while(transaction_find_client_by_token(token, 4, destination_addr, destination_port)); 00391 memcpy(transaction_ptr->token,token,4); 00392 transaction_ptr->token_len = 4; 00393 request.token_ptr = transaction_ptr->token; 00394 request.token_len = 4; 00395 00396 request.payload_len = payload_len; 00397 request.payload_ptr = (uint8_t *) payload_ptr; // Cast away const and trust that nsdl doesn't modify... 00398 00399 prepare_blockwise_message(handle->coap, &request); 00400 00401 data_len = sn_coap_builder_calc_needed_packet_data_size_2(&request, sn_coap_protocol_get_configured_blockwise_size(handle->coap)); 00402 data_ptr = own_alloc(data_len); 00403 if(data_len > 0 && !data_ptr){ 00404 transaction_delete(transaction_ptr); 00405 return 0; 00406 } 00407 int16_t sn_coap_ret = sn_coap_protocol_build(handle->coap, &dst_addr, data_ptr, &request, transaction_ptr); 00408 if (sn_coap_ret == -4) { 00409 /* 00410 * Not able to add message to resend queue, adjust message lifetime to one resending 00411 */ 00412 transaction_ptr->valid_until = coap_service_get_internal_timer_ticks() + COAP_RESENDING_INTERVAL; 00413 } else if (sn_coap_ret < 0) { 00414 /* 00415 * Failed to build message, set transaction validity time to minimum to get this transaction cleared 00416 * immediately and callback called. 00417 */ 00418 transaction_ptr->valid_until = coap_service_get_internal_timer_ticks(); 00419 } 00420 00421 transaction_ptr->msg_id = request.msg_id; 00422 handle->sn_coap_tx_callback(data_ptr, data_len, &dst_addr, transaction_ptr); 00423 00424 // Free allocated data 00425 own_free(data_ptr); 00426 if(request.options_list_ptr) { 00427 own_free(request.options_list_ptr); 00428 } 00429 00430 if(request_response_cb == NULL){ 00431 //No response expected 00432 return 0; 00433 } 00434 return request.msg_id; 00435 } 00436 00437 static int8_t coap_message_handler_resp_build_and_send(coap_msg_handler_t *handle, sn_coap_hdr_s *coap_msg_ptr, coap_transaction_t *transaction_ptr) 00438 { 00439 sn_nsdl_addr_s dst_addr; 00440 uint16_t data_len; 00441 uint8_t *data_ptr; 00442 00443 00444 dst_addr.addr_ptr = transaction_ptr->remote_address; 00445 dst_addr.addr_len = 16; 00446 dst_addr.type = SN_NSDL_ADDRESS_TYPE_IPV6; 00447 dst_addr.port = transaction_ptr->remote_port; 00448 #if SN_COAP_MAX_BLOCKWISE_PAYLOAD_SIZE 00449 prepare_blockwise_message(handle->coap, coap_msg_ptr); 00450 #endif 00451 data_len = sn_coap_builder_calc_needed_packet_data_size_2(coap_msg_ptr, sn_coap_protocol_get_configured_blockwise_size(handle->coap)); 00452 data_ptr = own_alloc(data_len); 00453 if (data_len > 0 && !data_ptr) { 00454 return -1; 00455 } 00456 sn_coap_protocol_build(handle->coap, &dst_addr, data_ptr, coap_msg_ptr, transaction_ptr); 00457 00458 handle->sn_coap_tx_callback(data_ptr, data_len, &dst_addr, transaction_ptr); 00459 own_free(data_ptr); 00460 00461 return 0; 00462 00463 } 00464 00465 int8_t coap_message_handler_response_send(coap_msg_handler_t *handle, int8_t service_id, uint8_t options, sn_coap_hdr_s *request_ptr, sn_coap_msg_code_e message_code, sn_coap_content_format_e content_type, const uint8_t *payload_ptr, uint16_t payload_len) 00466 { 00467 sn_coap_hdr_s *response; 00468 coap_transaction_t *transaction_ptr; 00469 int8_t ret_val = 0; 00470 (void) options; 00471 (void)service_id; 00472 00473 tr_debug("Service %d, send CoAP response", service_id); 00474 if (!request_ptr || !handle) { 00475 tr_error("invalid params"); 00476 return -1; 00477 } 00478 00479 transaction_ptr = transaction_find_server(request_ptr->msg_id); 00480 00481 if (!transaction_ptr) { 00482 tr_error("response transaction not found"); 00483 return -2; 00484 } 00485 00486 response = sn_coap_build_response(handle->coap, request_ptr, message_code); 00487 if( !response ){ 00488 return -1; 00489 } 00490 response->payload_len = payload_len; 00491 response->payload_ptr = (uint8_t *) payload_ptr; // Cast away const and trust that nsdl doesn't modify... 00492 response->content_format = content_type; 00493 00494 00495 ret_val = coap_message_handler_resp_build_and_send(handle, response, transaction_ptr); 00496 sn_coap_parser_release_allocated_coap_msg_mem(handle->coap, response); 00497 if(ret_val == 0) { 00498 transaction_delete(transaction_ptr); 00499 } 00500 00501 return ret_val; 00502 } 00503 00504 int8_t coap_message_handler_response_send_by_msg_id(coap_msg_handler_t *handle, int8_t service_id, uint8_t options, uint16_t msg_id, sn_coap_msg_code_e message_code, sn_coap_content_format_e content_type, const uint8_t *payload_ptr,uint16_t payload_len) 00505 { 00506 sn_coap_hdr_s response; 00507 coap_transaction_t *transaction_ptr; 00508 int8_t ret_val; 00509 00510 (void) options; 00511 (void)service_id; 00512 00513 transaction_ptr = transaction_find_server(msg_id); 00514 if (!transaction_ptr || !handle) { 00515 return -1; 00516 } 00517 00518 tr_debug("Service %d, send CoAP response", service_id); 00519 00520 memset(&response, 0, sizeof(sn_coap_hdr_s)); 00521 00522 response.payload_len = payload_len; 00523 response.payload_ptr = (uint8_t *) payload_ptr; // Cast away const and trust that nsdl doesn't modify... 00524 response.content_format = content_type; 00525 response.token_len = transaction_ptr->token_len; 00526 response.token_ptr = transaction_ptr->token; 00527 response.msg_code = message_code; 00528 if (transaction_ptr->req_msg_type == COAP_MSG_TYPE_CONFIRMABLE) { 00529 response.msg_type = COAP_MSG_TYPE_ACKNOWLEDGEMENT; 00530 response.msg_id = msg_id; 00531 } else { 00532 response.msg_type = COAP_MSG_TYPE_NON_CONFIRMABLE; 00533 } 00534 00535 ret_val = coap_message_handler_resp_build_and_send(handle, &response, transaction_ptr); 00536 if(ret_val == 0) { 00537 transaction_delete(transaction_ptr); 00538 } 00539 00540 return ret_val; 00541 } 00542 00543 int8_t coap_message_handler_request_delete(coap_msg_handler_t *handle, int8_t service_id, uint16_t msg_id) 00544 { 00545 coap_transaction_t *transaction_ptr; 00546 (void)service_id; 00547 00548 00549 tr_debug("Service %d, delete CoAP request %d", service_id, msg_id); 00550 if (!handle) { 00551 tr_error("invalid params"); 00552 return -1; 00553 } 00554 sn_coap_protocol_delete_retransmission(handle->coap, msg_id); 00555 00556 transaction_ptr = transaction_find_client(msg_id); 00557 if (!transaction_ptr) { 00558 tr_error("response transaction not found"); 00559 return -2; 00560 } 00561 transaction_delete(transaction_ptr); 00562 return 0; 00563 } 00564 00565 int8_t coap_message_handler_exec(coap_msg_handler_t *handle, uint32_t current_time){ 00566 00567 if( !handle ){ 00568 return -1; 00569 } 00570 00571 // Remove outdated transactions from queue 00572 ns_list_foreach_safe(coap_transaction_t, transaction, &request_list) { 00573 if (transaction->valid_until < current_time) { 00574 tr_debug("transaction %d timed out", transaction->msg_id); 00575 ns_list_remove(&request_list, transaction); 00576 if (transaction->resp_cb) { 00577 transaction->resp_cb(transaction->service_id, transaction->remote_address, transaction->remote_port, NULL); 00578 } 00579 transaction_free(transaction); 00580 } 00581 } 00582 00583 return sn_coap_protocol_exec(handle->coap, current_time); 00584 }
Generated on Tue Aug 9 2022 00:37:04 by
1.7.2