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.
m2mobject.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 00017 // Note: this macro is needed on armcc to get the the PRI*32 macros 00018 // from inttypes.h in a C++ code. 00019 #ifndef __STDC_FORMAT_MACROS 00020 #define __STDC_FORMAT_MACROS 00021 #endif 00022 #include <inttypes.h> 00023 00024 #include "mbed-client/m2mobject.h" 00025 #include "mbed-client/m2mendpoint.h" 00026 #include "mbed-client/m2mconstants.h" 00027 #include "include/m2mtlvserializer.h" 00028 #include "include/m2mtlvdeserializer.h" 00029 #include "include/m2mreporthandler.h" 00030 #include "mbed-trace/mbed_trace.h" 00031 #include "mbed-client/m2mstringbuffer.h" 00032 #include "include/m2mcallbackstorage.h" 00033 00034 #include <stdlib.h> 00035 00036 #define BUFFER_SIZE 10 00037 #define TRACE_GROUP "mClt" 00038 00039 M2MObject::M2MObject(const String &object_name, char *path, bool external_blockwise_store) 00040 : M2MBase(object_name, 00041 M2MBase::Dynamic, 00042 #ifndef DISABLE_RESOURCE_TYPE 00043 "", 00044 #endif 00045 path, 00046 external_blockwise_store, 00047 false), 00048 _observation_handler(NULL) 00049 #ifdef MBED_CLOUD_CLIENT_EDGE_EXTENSION 00050 ,_endpoint(NULL) 00051 #endif 00052 { 00053 M2MBase::set_base_type(M2MBase::Object); 00054 M2MBase::set_operation(M2MBase::GET_ALLOWED); 00055 if(M2MBase::name_id() != -1) { 00056 M2MBase::set_coap_content_type(COAP_CONTENT_OMA_TLV_TYPE_OLD); 00057 } 00058 } 00059 00060 M2MObject::M2MObject(const M2MBase::lwm2m_parameters_s* static_res) 00061 : M2MBase(static_res), 00062 _observation_handler(NULL) 00063 #ifdef MBED_CLOUD_CLIENT_EDGE_EXTENSION 00064 ,_endpoint(NULL) 00065 #endif 00066 { 00067 M2MBase::set_operation(M2MBase::GET_ALLOWED); 00068 if(M2MBase::name_id() != -1) { 00069 M2MBase::set_coap_content_type(COAP_CONTENT_OMA_TLV_TYPE_OLD); 00070 } 00071 } 00072 00073 M2MObject::~M2MObject() 00074 { 00075 if(!_instance_list.empty()) { 00076 00077 M2MObjectInstanceList::const_iterator it; 00078 it = _instance_list.begin(); 00079 M2MObjectInstance* obj = NULL; 00080 uint16_t index = 0; 00081 for (; it!=_instance_list.end(); it++, index++ ) { 00082 //Free allocated memory for object instances. 00083 obj = *it; 00084 delete obj; 00085 } 00086 00087 _instance_list.clear(); 00088 } 00089 00090 free_resources(); 00091 } 00092 00093 M2MObjectInstance* M2MObject::create_object_instance(uint16_t instance_id) 00094 { 00095 tr_debug("M2MObject::create_object_instance - id: %d", instance_id); 00096 M2MObjectInstance *instance = NULL; 00097 if(!object_instance(instance_id)) { 00098 char* path = create_path(*this, instance_id); 00099 if (path) { 00100 // Note: the object instance's name contains actually object's name. 00101 instance = new M2MObjectInstance(*this, "", path); 00102 if(instance) { 00103 instance->add_observation_level(observation_level()); 00104 instance->set_instance_id(instance_id); 00105 if(M2MBase::name_id() != -1) { 00106 instance->set_coap_content_type(COAP_CONTENT_OMA_TLV_TYPE_OLD); 00107 } 00108 _instance_list.push_back(instance); 00109 set_changed(); 00110 } 00111 } 00112 } 00113 return instance; 00114 } 00115 00116 00117 M2MObjectInstance* M2MObject::create_object_instance(const lwm2m_parameters_s* s) 00118 { 00119 tr_debug("M2MObject::create_object_instance - id: %d", s->identifier.instance_id); 00120 M2MObjectInstance *instance = NULL; 00121 if(!object_instance(s->identifier.instance_id)) { 00122 00123 instance = new M2MObjectInstance(*this, s); 00124 if(instance) { 00125 instance->add_observation_level(observation_level()); 00126 //instance->set_instance_id(instance_id); 00127 //if(M2MBase::name_id() != -1) { 00128 // instance->set_coap_content_type(COAP_CONTENT_OMA_TLV_TYPE_OLD); 00129 //} 00130 _instance_list.push_back(instance); 00131 set_changed(); 00132 } 00133 } 00134 return instance; 00135 } 00136 00137 bool M2MObject::remove_object_instance(uint16_t inst_id) 00138 { 00139 tr_debug("M2MObject::remove_object_instance(inst_id %d)", inst_id); 00140 bool success = false; 00141 if(!_instance_list.empty()) { 00142 M2MObjectInstance* obj = NULL; 00143 M2MObjectInstanceList::const_iterator it; 00144 it = _instance_list.begin(); 00145 int pos = 0; 00146 for ( ; it != _instance_list.end(); it++, pos++ ) { 00147 if((*it)->instance_id() == inst_id) { 00148 // Instance found and deleted. 00149 obj = *it; 00150 00151 _instance_list.erase(pos); 00152 delete obj; 00153 success = true; 00154 set_changed(); 00155 break; 00156 } 00157 } 00158 } 00159 return success; 00160 } 00161 00162 M2MObjectInstance* M2MObject::object_instance(uint16_t inst_id) const 00163 { 00164 tr_debug("M2MObject::object_instance(inst_id %d)", inst_id); 00165 M2MObjectInstance *obj = NULL; 00166 if(!_instance_list.empty()) { 00167 M2MObjectInstanceList::const_iterator it; 00168 it = _instance_list.begin(); 00169 for ( ; it != _instance_list.end(); it++ ) { 00170 if((*it)->instance_id() == inst_id) { 00171 // Instance found. 00172 obj = *it; 00173 break; 00174 } 00175 } 00176 } 00177 return obj; 00178 } 00179 00180 const M2MObjectInstanceList& M2MObject::instances() const 00181 { 00182 return _instance_list; 00183 } 00184 00185 uint16_t M2MObject::instance_count() const 00186 { 00187 return (uint16_t)_instance_list.size(); 00188 } 00189 00190 M2MObservationHandler* M2MObject::observation_handler() const 00191 { 00192 // XXX: need to check the flag too 00193 return _observation_handler; 00194 } 00195 00196 void M2MObject::set_observation_handler(M2MObservationHandler *handler) 00197 { 00198 tr_debug("M2MObject::set_observation_handler - handler: 0x%p", (void*)handler); 00199 _observation_handler = handler; 00200 } 00201 00202 void M2MObject::add_observation_level(M2MBase::Observation observation_level) 00203 { 00204 M2MBase::add_observation_level(observation_level); 00205 if(!_instance_list.empty()) { 00206 M2MObjectInstanceList::const_iterator it; 00207 it = _instance_list.begin(); 00208 for ( ; it != _instance_list.end(); it++ ) { 00209 (*it)->add_observation_level(observation_level); 00210 } 00211 } 00212 } 00213 00214 void M2MObject::remove_observation_level(M2MBase::Observation observation_level) 00215 { 00216 M2MBase::remove_observation_level(observation_level); 00217 if(!_instance_list.empty()) { 00218 M2MObjectInstanceList::const_iterator it; 00219 it = _instance_list.begin(); 00220 for ( ; it != _instance_list.end(); it++ ) { 00221 (*it)->remove_observation_level(observation_level); 00222 } 00223 } 00224 } 00225 00226 sn_coap_hdr_s* M2MObject::handle_get_request(nsdl_s *nsdl, 00227 sn_coap_hdr_s *received_coap_header, 00228 M2MObservationHandler *observation_handler) 00229 { 00230 tr_info("M2MObject::handle_get_request()"); 00231 sn_coap_msg_code_e msg_code = COAP_MSG_CODE_RESPONSE_CONTENT; 00232 sn_coap_hdr_s * coap_response = sn_nsdl_build_response(nsdl, 00233 received_coap_header, 00234 msg_code); 00235 uint8_t *data = NULL; 00236 uint32_t data_length = 0; 00237 if(received_coap_header) { 00238 // process the GET if we have registered a callback for it 00239 if ((operation() & SN_GRS_GET_ALLOWED) != 0) { 00240 if(coap_response) { 00241 bool content_type_present = false; 00242 bool is_content_type_supported = true; 00243 00244 if (received_coap_header->options_list_ptr && 00245 received_coap_header->options_list_ptr->accept != COAP_CT_NONE) { 00246 content_type_present = true; 00247 coap_response->content_format = received_coap_header->options_list_ptr->accept; 00248 00249 } 00250 00251 // Check if preferred content type is supported 00252 if (content_type_present) { 00253 if (coap_response->content_format != COAP_CONTENT_OMA_TLV_TYPE_OLD && 00254 coap_response->content_format != COAP_CONTENT_OMA_TLV_TYPE) { 00255 is_content_type_supported = false; 00256 } 00257 } 00258 00259 if (is_content_type_supported) { 00260 if(!content_type_present && 00261 (M2MBase::coap_content_type() == COAP_CONTENT_OMA_TLV_TYPE || 00262 M2MBase::coap_content_type() == COAP_CONTENT_OMA_TLV_TYPE_OLD)) { 00263 coap_response->content_format = sn_coap_content_format_e(M2MBase::coap_content_type()); 00264 } 00265 00266 // fill in the CoAP response payload 00267 if(COAP_CONTENT_OMA_TLV_TYPE == coap_response->content_format || 00268 COAP_CONTENT_OMA_TLV_TYPE_OLD == coap_response->content_format) { 00269 set_coap_content_type(coap_response->content_format); 00270 data = M2MTLVSerializer::serialize(_instance_list, data_length); 00271 } 00272 00273 coap_response->payload_len = data_length; 00274 coap_response->payload_ptr = data; 00275 if(data){ 00276 coap_response->options_list_ptr = sn_nsdl_alloc_options_list(nsdl, coap_response); 00277 if(coap_response->options_list_ptr){ 00278 coap_response->options_list_ptr->max_age = max_age(); 00279 } 00280 if(received_coap_header->options_list_ptr) { 00281 if(received_coap_header->options_list_ptr->observe != -1) { 00282 if (is_observable()) { 00283 uint32_t number = 0; 00284 uint8_t observe_option = 0; 00285 observe_option = received_coap_header->options_list_ptr->observe; 00286 00287 if(START_OBSERVATION == observe_option) { 00288 // If the observe length is 0 means register for observation. 00289 if(received_coap_header->options_list_ptr->observe != -1) { 00290 number = received_coap_header->options_list_ptr->observe; 00291 } 00292 00293 // If the observe value is 0 means register for observation. 00294 if(number == 0) { 00295 tr_info("M2MObject::handle_get_request - put resource under observation"); 00296 set_under_observation(true,observation_handler); 00297 add_observation_level(M2MBase::O_Attribute); 00298 send_notification_delivery_status(*this, NOTIFICATION_STATUS_SUBSCRIBED); 00299 if(coap_response->options_list_ptr){ 00300 coap_response->options_list_ptr->observe = observation_number(); 00301 } 00302 } 00303 00304 if(received_coap_header->token_ptr) { 00305 set_observation_token(received_coap_header->token_ptr, 00306 received_coap_header->token_len); 00307 } 00308 } else if (STOP_OBSERVATION == observe_option) { 00309 tr_info("M2MObject::handle_get_request - stops observation"); 00310 // If the observe options_list_ptr->observe value is 1 means de-register from observation. 00311 set_under_observation(false, NULL); 00312 remove_observation_level(M2MBase::O_Attribute); 00313 send_notification_delivery_status(*this, NOTIFICATION_STATUS_UNSUBSCRIBED); 00314 } 00315 msg_code = COAP_MSG_CODE_RESPONSE_CONTENT; 00316 } 00317 else { 00318 msg_code = COAP_MSG_CODE_RESPONSE_METHOD_NOT_ALLOWED; 00319 } 00320 } 00321 } 00322 } else { 00323 msg_code = COAP_MSG_CODE_RESPONSE_UNSUPPORTED_CONTENT_FORMAT; // Content format not supported 00324 } 00325 } else { 00326 tr_error("M2MObject::handle_get_request() - Content-Type %d not supported", coap_response->content_format); 00327 msg_code = COAP_MSG_CODE_RESPONSE_NOT_ACCEPTABLE; 00328 } 00329 } 00330 }else { 00331 tr_error("M2MResource::handle_get_request - Return COAP_MSG_CODE_RESPONSE_METHOD_NOT_ALLOWED"); 00332 // Operation is not allowed. 00333 msg_code = COAP_MSG_CODE_RESPONSE_METHOD_NOT_ALLOWED; 00334 } 00335 } else { 00336 msg_code = COAP_MSG_CODE_RESPONSE_METHOD_NOT_ALLOWED; 00337 } 00338 if(coap_response) { 00339 coap_response->msg_code = msg_code; 00340 } 00341 return coap_response; 00342 } 00343 00344 sn_coap_hdr_s* M2MObject::handle_put_request(nsdl_s *nsdl, 00345 sn_coap_hdr_s *received_coap_header, 00346 M2MObservationHandler */*observation_handler*/, 00347 bool &/*execute_value_updated*/) 00348 { 00349 tr_info("M2MObject::handle_put_request()"); 00350 sn_coap_msg_code_e msg_code = COAP_MSG_CODE_RESPONSE_CHANGED; // 2.04 00351 sn_coap_hdr_s *coap_response = sn_nsdl_build_response(nsdl, 00352 received_coap_header, 00353 msg_code); 00354 if(received_coap_header) { 00355 if(received_coap_header->content_format != COAP_CT_NONE) { 00356 set_coap_content_type(received_coap_header->content_format); 00357 } 00358 if(received_coap_header->options_list_ptr && 00359 received_coap_header->options_list_ptr->uri_query_ptr) { 00360 char *query = (char*)alloc_string_copy(received_coap_header->options_list_ptr->uri_query_ptr, 00361 received_coap_header->options_list_ptr->uri_query_len); 00362 00363 if (query){ 00364 tr_info("M2MObject::handle_put_request() - query %s", query); 00365 // if anything was updated, re-initialize the stored notification attributes 00366 if (!handle_observation_attribute(query)){ 00367 tr_debug("M2MObject::handle_put_request() - Invalid query"); 00368 msg_code = COAP_MSG_CODE_RESPONSE_BAD_REQUEST; // 4.00 00369 } 00370 free(query); 00371 } 00372 } else { 00373 tr_error("M2MObject::handle_put_request() - COAP_MSG_CODE_RESPONSE_BAD_REQUEST - Empty URI_QUERY"); 00374 msg_code = COAP_MSG_CODE_RESPONSE_BAD_REQUEST; 00375 } 00376 } else { 00377 msg_code = COAP_MSG_CODE_RESPONSE_METHOD_NOT_ALLOWED; 00378 } 00379 if(coap_response) { 00380 coap_response->msg_code = msg_code; 00381 } 00382 return coap_response; 00383 } 00384 00385 00386 sn_coap_hdr_s* M2MObject::handle_post_request(nsdl_s *nsdl, 00387 sn_coap_hdr_s *received_coap_header, 00388 M2MObservationHandler *observation_handler, 00389 bool &execute_value_updated, 00390 sn_nsdl_addr_s *) 00391 { 00392 tr_info("M2MObject::handle_post_request()"); 00393 sn_coap_msg_code_e msg_code = COAP_MSG_CODE_RESPONSE_CHANGED; // 2.04 00394 // process the POST if we have registered a callback for it 00395 sn_coap_hdr_s *coap_response = sn_nsdl_build_response(nsdl, 00396 received_coap_header, 00397 msg_code); 00398 00399 if (received_coap_header) { 00400 if ((operation() & SN_GRS_POST_ALLOWED) != 0) { 00401 if (received_coap_header->content_format != COAP_CT_NONE) { 00402 set_coap_content_type(received_coap_header->content_format); 00403 } 00404 if(received_coap_header->payload_ptr) { 00405 tr_debug("M2MObject::handle_post_request() - Update Object with new values"); 00406 uint16_t coap_content_type = 0; 00407 bool content_type_present = false; 00408 if(received_coap_header->content_format != COAP_CT_NONE) { 00409 content_type_present = true; 00410 if(coap_response) { 00411 coap_content_type = received_coap_header->content_format; 00412 } 00413 } // if(received_coap_header->content_format) 00414 if(!content_type_present && 00415 (M2MBase::coap_content_type() == COAP_CONTENT_OMA_TLV_TYPE || 00416 M2MBase::coap_content_type() == COAP_CONTENT_OMA_TLV_TYPE_OLD)) { 00417 coap_content_type = M2MBase::coap_content_type(); 00418 } 00419 00420 tr_debug("M2MObject::handle_post_request() - Request Content-type: %d", coap_content_type); 00421 00422 if(COAP_CONTENT_OMA_TLV_TYPE == coap_content_type || 00423 COAP_CONTENT_OMA_TLV_TYPE_OLD == coap_content_type) { 00424 set_coap_content_type(coap_content_type); 00425 uint32_t instance_id = 0; 00426 // Check next free instance id 00427 for(instance_id = 0; instance_id <= MAX_UNINT_16_COUNT; instance_id++) { 00428 if(NULL == object_instance(instance_id)) { 00429 break; 00430 } 00431 if(instance_id == MAX_UNINT_16_COUNT) { 00432 msg_code = COAP_MSG_CODE_RESPONSE_METHOD_NOT_ALLOWED; 00433 break; 00434 } 00435 } 00436 if(COAP_MSG_CODE_RESPONSE_CHANGED == msg_code) { 00437 bool is_obj_instance = false; 00438 bool obj_instance_exists = false; 00439 is_obj_instance = M2MTLVDeserializer::is_object_instance(received_coap_header->payload_ptr); 00440 if (is_obj_instance) { 00441 instance_id = M2MTLVDeserializer::instance_id(received_coap_header->payload_ptr); 00442 tr_debug("M2MObject::handle_post_request() - instance id in TLV: %" PRIu32, instance_id); 00443 // Check if instance id already exists 00444 if (object_instance(instance_id)){ 00445 obj_instance_exists = true; 00446 } 00447 } 00448 if (!obj_instance_exists && coap_response) { 00449 M2MObjectInstance *obj_instance = create_object_instance(instance_id); 00450 if (obj_instance) { 00451 obj_instance->set_operation(M2MBase::GET_PUT_ALLOWED); 00452 } 00453 00454 M2MTLVDeserializer::Error error = M2MTLVDeserializer::None; 00455 if(is_obj_instance) { 00456 tr_debug("M2MObject::handle_post_request() - TLV data contains ObjectInstance"); 00457 error = M2MTLVDeserializer::deserialise_object_instances(received_coap_header->payload_ptr, 00458 received_coap_header->payload_len, 00459 *this, 00460 M2MTLVDeserializer::Post); 00461 } else if(obj_instance && 00462 (M2MTLVDeserializer::is_resource(received_coap_header->payload_ptr) || 00463 M2MTLVDeserializer::is_multiple_resource(received_coap_header->payload_ptr))) { 00464 tr_debug("M2MObject::handle_post_request() - TLV data contains Resources"); 00465 error = M2MTLVDeserializer::deserialize_resources(received_coap_header->payload_ptr, 00466 received_coap_header->payload_len, 00467 *obj_instance, 00468 M2MTLVDeserializer::Post); 00469 } else { 00470 error = M2MTLVDeserializer::NotValid; 00471 } 00472 switch(error) { 00473 case M2MTLVDeserializer::None: 00474 if(observation_handler) { 00475 execute_value_updated = true; 00476 } 00477 coap_response->options_list_ptr = sn_nsdl_alloc_options_list(nsdl, coap_response); 00478 00479 if (coap_response->options_list_ptr) { 00480 00481 StringBuffer<MAX_OBJECT_PATH_NAME> obj_name; 00482 00483 if (obj_name.ensure_space(M2MBase::resource_name_length() + (1 + 5 + 1))) { 00484 obj_name.append(M2MBase::name()); 00485 obj_name.append('/'); 00486 obj_name.append_int(instance_id); 00487 00488 coap_response->options_list_ptr->location_path_len = obj_name.get_size(); 00489 coap_response->options_list_ptr->location_path_ptr = 00490 alloc_copy((uint8_t*)obj_name.c_str(), obj_name.get_size()); 00491 // todo: else return error 00492 } 00493 } 00494 // todo: else return error 00495 msg_code = COAP_MSG_CODE_RESPONSE_CREATED; 00496 break; 00497 case M2MTLVDeserializer::NotAllowed: 00498 msg_code = COAP_MSG_CODE_RESPONSE_METHOD_NOT_ALLOWED; 00499 break; 00500 case M2MTLVDeserializer::NotValid: 00501 msg_code = COAP_MSG_CODE_RESPONSE_BAD_REQUEST; 00502 break; 00503 case M2MTLVDeserializer::NotFound: 00504 msg_code = COAP_MSG_CODE_RESPONSE_NOT_FOUND; 00505 break; 00506 case M2MTLVDeserializer::OutOfMemory: 00507 msg_code = COAP_MSG_CODE_RESPONSE_REQUEST_ENTITY_TOO_LARGE; 00508 break; 00509 } 00510 00511 } else { 00512 tr_error("M2MObject::handle_post_request() - COAP_MSG_CODE_RESPONSE_BAD_REQUEST"); 00513 msg_code = COAP_MSG_CODE_RESPONSE_BAD_REQUEST; 00514 } 00515 } 00516 } else { 00517 msg_code =COAP_MSG_CODE_RESPONSE_UNSUPPORTED_CONTENT_FORMAT; 00518 } // if(COAP_CONTENT_OMA_TLV_TYPE == coap_content_type) 00519 } else { 00520 tr_error("M2MObject::handle_post_request - COAP_MSG_CODE_RESPONSE_BAD_REQUEST - Missing Payload"); 00521 msg_code = COAP_MSG_CODE_RESPONSE_BAD_REQUEST; // 00522 } 00523 } else { // if ((object->operation() & SN_GRS_POST_ALLOWED) != 0) 00524 tr_error("M2MObject::handle_post_request - COAP_MSG_CODE_RESPONSE_METHOD_NOT_ALLOWED"); 00525 msg_code = COAP_MSG_CODE_RESPONSE_METHOD_NOT_ALLOWED; // 4.05 00526 } 00527 } else { //if(received_coap_header) 00528 tr_error("M2MObject::handle_post_request - COAP_MSG_CODE_RESPONSE_METHOD_NOT_ALLOWED"); 00529 msg_code = COAP_MSG_CODE_RESPONSE_METHOD_NOT_ALLOWED; // 4.05 00530 } 00531 00532 if(coap_response) { 00533 coap_response->msg_code = msg_code; 00534 } 00535 return coap_response; 00536 } 00537 00538 void M2MObject::notification_update(uint16_t obj_instance_id) 00539 { 00540 tr_debug("M2MObject::notification_update - id: %d", obj_instance_id); 00541 M2MReportHandler *report_handler = M2MBase::report_handler(); 00542 if(report_handler && is_under_observation()) { 00543 report_handler->set_notification_trigger(obj_instance_id); 00544 } 00545 } 00546 00547 #ifdef MBED_CLOUD_CLIENT_EDGE_EXTENSION 00548 void M2MObject::set_endpoint(M2MEndpoint *endpoint) 00549 { 00550 _endpoint = endpoint; 00551 } 00552 00553 M2MEndpoint* M2MObject::get_endpoint() const 00554 { 00555 return _endpoint; 00556 } 00557 #endif 00558 00559 M2MBase *M2MObject::get_parent() const 00560 { 00561 #ifdef MBED_CLOUD_CLIENT_EDGE_EXTENSION 00562 return (M2MBase *) get_endpoint(); 00563 #else 00564 return NULL; 00565 #endif 00566 }
Generated on Tue Jul 12 2022 16:24:15 by
1.7.2