1 /** 2 * Copyright © 2018 Intel Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #pragma once 17 #include <cxxabi.h> 18 19 #include <boost/asio/spawn.hpp> 20 #include <boost/callable_traits.hpp> 21 #include <ipmid/api-types.hpp> 22 #include <ipmid/message.hpp> 23 #include <phosphor-logging/log.hpp> 24 #include <user_channel/channel_layer.hpp> 25 26 #include <algorithm> 27 #include <cstdint> 28 #include <exception> 29 #include <memory> 30 #include <optional> 31 #include <stdexcept> 32 #include <tuple> 33 #include <utility> 34 35 #ifdef ALLOW_DEPRECATED_API 36 #include <ipmid/api.h> 37 38 #include <ipmid/oemrouter.hpp> 39 #endif /* ALLOW_DEPRECATED_API */ 40 41 namespace ipmi 42 { 43 44 template <typename... Args> 45 static inline message::Response::ptr 46 errorResponse(message::Request::ptr request, ipmi::Cc cc, Args&&... args) 47 { 48 message::Response::ptr response = request->makeResponse(); 49 response->cc = cc; 50 response->pack(args...); 51 return response; 52 } 53 static inline message::Response::ptr 54 errorResponse(message::Request::ptr request, ipmi::Cc cc) 55 { 56 message::Response::ptr response = request->makeResponse(); 57 response->cc = cc; 58 return response; 59 } 60 61 /** @brief Exception extension that allows setting an IPMI return code */ 62 class HandlerCompletion 63 { 64 public: 65 HandlerCompletion(Cc cc) noexcept : cc(cc) {} 66 67 Cc code() const noexcept 68 { 69 return cc; 70 } 71 72 private: 73 Cc cc; 74 }; 75 76 /** @brief Exception extension that allows setting an IPMI return code and 77 * printing out a logged error */ 78 class HandlerException : public HandlerCompletion, public std::runtime_error 79 { 80 public: 81 HandlerException(Cc cc, const char* what) : 82 HandlerCompletion(cc), std::runtime_error(what) 83 {} 84 HandlerException(Cc cc, const std::string& what) : 85 HandlerException(cc, what.c_str()) 86 {} 87 }; 88 89 static inline const char* currentExceptionType() 90 { 91 int status; 92 return abi::__cxa_demangle(abi::__cxa_current_exception_type()->name(), 0, 93 0, &status); 94 } 95 96 /** 97 * @brief Handler base class for dealing with IPMI request/response 98 * 99 * The subclasses are all templated so they can provide access to any type 100 * of command callback functions. 101 */ 102 class HandlerBase 103 { 104 public: 105 using ptr = std::shared_ptr<HandlerBase>; 106 107 virtual ~HandlerBase() = default; 108 109 /** @brief wrap the call to the registered handler with the request 110 * 111 * This is called from the running queue context after it has already 112 * created a request object that contains all the information required to 113 * execute the ipmi command. This function will return the response object 114 * pointer that owns the response object that will ultimately get sent back 115 * to the requester. 116 * 117 * This is a non-virtual function wrapper to the virtualized executeCallback 118 * function that actually does the work. This is required because of how 119 * templates and virtualization work together. 120 * 121 * @param request a shared_ptr to a Request object 122 * 123 * @return a shared_ptr to a Response object 124 */ 125 message::Response::ptr call(message::Request::ptr request) 126 { 127 return executeCallback(request); 128 } 129 130 private: 131 /** @brief call the registered handler with the request 132 * 133 * This is called from the running queue context after it has already 134 * created a request object that contains all the information required to 135 * execute the ipmi command. This function will return the response object 136 * pointer that owns the response object that will ultimately get sent back 137 * to the requester. 138 * 139 * @param request a shared_ptr to a Request object 140 * 141 * @return a shared_ptr to a Response object 142 */ 143 virtual message::Response::ptr 144 executeCallback(message::Request::ptr request) = 0; 145 }; 146 147 /** 148 * @brief Main IPMI handler class 149 * 150 * New IPMI handlers will resolve into this class, which will read the signature 151 * of the registering function, attempt to extract the appropriate arguments 152 * from a request, pass the arguments to the function, and then pack the 153 * response of the function back into an IPMI response. 154 */ 155 template <typename Handler> 156 class IpmiHandler final : public HandlerBase 157 { 158 public: 159 explicit IpmiHandler(Handler&& handler) : 160 handler_(std::forward<Handler>(handler)) 161 {} 162 163 private: 164 Handler handler_; 165 166 /** @brief call the registered handler with the request 167 * 168 * This is called from the running queue context after it has already 169 * created a request object that contains all the information required to 170 * execute the ipmi command. This function will return the response object 171 * pointer that owns the response object that will ultimately get sent back 172 * to the requester. 173 * 174 * Because this is the new variety of IPMI handler, this is the function 175 * that attempts to extract the requested parameters in order to pass them 176 * onto the callback function and then packages up the response into a plain 177 * old vector to pass back to the caller. 178 * 179 * @param request a shared_ptr to a Request object 180 * 181 * @return a shared_ptr to a Response object 182 */ 183 message::Response::ptr 184 executeCallback(message::Request::ptr request) override 185 { 186 message::Response::ptr response = request->makeResponse(); 187 188 using CallbackSig = boost::callable_traits::args_t<Handler>; 189 using InputArgsType = typename utility::DecayTuple<CallbackSig>::type; 190 using UnpackArgsType = typename utility::StripFirstArgs< 191 utility::NonIpmiArgsCount<InputArgsType>::size(), 192 InputArgsType>::type; 193 using ResultType = boost::callable_traits::return_type_t<Handler>; 194 195 UnpackArgsType unpackArgs{}; 196 request->payload.trailingOk = false; 197 ipmi::Cc unpackError = request->unpack(unpackArgs); 198 if (unpackError != ipmi::ccSuccess) 199 { 200 response->cc = unpackError; 201 return response; 202 } 203 /* callbacks can contain an optional first argument of one of: 204 * 1) boost::asio::yield_context 205 * 2) ipmi::Context::ptr 206 * 3) ipmi::message::Request::ptr 207 * 208 * If any of those is part of the callback signature as the first 209 * argument, it will automatically get packed into the parameter pack 210 * here. 211 * 212 * One more special optional argument is an ipmi::message::Payload. 213 * This argument can be in any position, though logically it makes the 214 * most sense if it is the last. If this class is included in the 215 * handler signature, it will allow for the handler to unpack optional 216 * parameters. For example, the Set LAN Configuration Parameters 217 * command takes variable length (and type) values for each of the LAN 218 * parameters. This means that the only fixed data is the channel and 219 * parameter selector. All the remaining data can be extracted using 220 * the Payload class and the unpack API available to the Payload class. 221 */ 222 ResultType result; 223 try 224 { 225 std::optional<InputArgsType> inputArgs; 226 if constexpr (std::tuple_size<InputArgsType>::value > 0) 227 { 228 if constexpr (std::is_same< 229 std::tuple_element_t<0, InputArgsType>, 230 boost::asio::yield_context>::value) 231 { 232 inputArgs.emplace(std::tuple_cat( 233 std::forward_as_tuple(request->ctx->yield), 234 std::move(unpackArgs))); 235 } 236 else if constexpr (std::is_same< 237 std::tuple_element_t<0, InputArgsType>, 238 ipmi::Context::ptr>::value) 239 { 240 inputArgs.emplace( 241 std::tuple_cat(std::forward_as_tuple(request->ctx), 242 std::move(unpackArgs))); 243 } 244 else if constexpr (std::is_same< 245 std::tuple_element_t<0, InputArgsType>, 246 ipmi::message::Request::ptr>::value) 247 { 248 inputArgs.emplace(std::tuple_cat( 249 std::forward_as_tuple(request), std::move(unpackArgs))); 250 } 251 else 252 { 253 // no special parameters were requested (but others were) 254 inputArgs.emplace(std::move(unpackArgs)); 255 } 256 } 257 else 258 { 259 // no parameters were requested 260 inputArgs = std::move(unpackArgs); 261 } 262 263 // g++ sometimes complains that *inputArgs might be uninitialized 264 // This is never the case. If the unpacker fails to fill every 265 // item in unpackArgs, this function returns early. So this is 266 // just to silence the build. 267 #pragma GCC diagnostic push // save current diagnostics state 268 #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" 269 // execute the registered callback function and get the 270 // ipmi::RspType<> 271 result = std::apply(handler_, *inputArgs); 272 #pragma GCC diagnostic pop // restore previous diagnostics state 273 } 274 catch (const HandlerException& e) 275 { 276 phosphor::logging::log<phosphor::logging::level::INFO>( 277 "Handler produced exception", 278 phosphor::logging::entry("CC=%x", e.code()), 279 phosphor::logging::entry("EXCEPTION=%s", e.what()), 280 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 281 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 282 return errorResponse(request, e.code()); 283 } 284 catch (const std::exception& e) 285 { 286 phosphor::logging::log<phosphor::logging::level::ERR>( 287 "Handler failed to catch exception", 288 phosphor::logging::entry("EXCEPTION=%s", e.what()), 289 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 290 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 291 return errorResponse(request, ccUnspecifiedError); 292 } 293 catch (const HandlerCompletion& c) 294 { 295 return errorResponse(request, c.code()); 296 } 297 catch (...) 298 { 299 const char* what = currentExceptionType(); 300 phosphor::logging::log<phosphor::logging::level::ERR>( 301 "Handler failed to catch exception", 302 phosphor::logging::entry("EXCEPTION=%s", what), 303 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 304 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 305 return errorResponse(request, ccUnspecifiedError); 306 } 307 308 response->cc = std::get<0>(result); 309 auto payload = std::get<1>(result); 310 // check for optional payload 311 if (payload) 312 { 313 response->pack(*payload); 314 } 315 return response; 316 } 317 }; 318 319 #ifdef ALLOW_DEPRECATED_API 320 static constexpr size_t maxLegacyBufferSize = 64 * 1024; 321 /** 322 * @brief Legacy IPMI handler class 323 * 324 * Legacy IPMI handlers will resolve into this class, which will behave the same 325 * way as the legacy IPMI queue, passing in a big buffer for the request and a 326 * big buffer for the response. 327 * 328 * As soon as all the handlers have been rewritten, this class will be marked as 329 * deprecated and eventually removed. 330 */ 331 template <> 332 class IpmiHandler<ipmid_callback_t> final : public HandlerBase 333 { 334 public: 335 explicit IpmiHandler(const ipmid_callback_t& handler, void* ctx = nullptr) : 336 handler_(handler), handlerCtx(ctx) 337 {} 338 339 private: 340 ipmid_callback_t handler_; 341 void* handlerCtx; 342 343 /** @brief call the registered handler with the request 344 * 345 * This is called from the running queue context after it has already 346 * created a request object that contains all the information required to 347 * execute the ipmi command. This function will return the response object 348 * pointer that owns the response object that will ultimately get sent back 349 * to the requester. 350 * 351 * Because this is the legacy variety of IPMI handler, this function does 352 * not really have to do much other than pass the payload to the callback 353 * and return response to the caller. 354 * 355 * @param request a shared_ptr to a Request object 356 * 357 * @return a shared_ptr to a Response object 358 */ 359 message::Response::ptr 360 executeCallback(message::Request::ptr request) override 361 { 362 message::Response::ptr response = request->makeResponse(); 363 // allocate a big response buffer here 364 response->payload.resize(maxLegacyBufferSize); 365 366 size_t len = request->payload.size() - request->payload.rawIndex; 367 Cc ccRet{ccSuccess}; 368 try 369 { 370 ccRet = 371 handler_(request->ctx->netFn, request->ctx->cmd, 372 request->payload.data() + request->payload.rawIndex, 373 response->payload.data(), &len, handlerCtx); 374 } 375 catch (const HandlerException& e) 376 { 377 phosphor::logging::log<phosphor::logging::level::INFO>( 378 "Legacy Handler produced exception", 379 phosphor::logging::entry("CC=%x", e.code()), 380 phosphor::logging::entry("EXCEPTION=%s", e.what()), 381 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 382 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 383 return errorResponse(request, e.code()); 384 } 385 catch (const std::exception& e) 386 { 387 phosphor::logging::log<phosphor::logging::level::ERR>( 388 "Legacy Handler failed to catch exception", 389 phosphor::logging::entry("EXCEPTION=%s", e.what()), 390 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 391 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 392 return errorResponse(request, ccUnspecifiedError); 393 } 394 catch (const HandlerCompletion& c) 395 { 396 return errorResponse(request, c.code()); 397 } 398 catch (...) 399 { 400 const char* what = currentExceptionType(); 401 phosphor::logging::log<phosphor::logging::level::ERR>( 402 "Handler failed to catch exception", 403 phosphor::logging::entry("EXCEPTION=%s", what), 404 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 405 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 406 return errorResponse(request, ccUnspecifiedError); 407 } 408 response->cc = ccRet; 409 response->payload.resize(len); 410 return response; 411 } 412 }; 413 414 /** 415 * @brief Legacy IPMI OEM handler class 416 * 417 * Legacy IPMI OEM handlers will resolve into this class, which will behave the 418 * same way as the legacy IPMI queue, passing in a big buffer for the request 419 * and a big buffer for the response. 420 * 421 * As soon as all the handlers have been rewritten, this class will be marked as 422 * deprecated and eventually removed. 423 */ 424 template <> 425 class IpmiHandler<oem::Handler> final : public HandlerBase 426 { 427 public: 428 explicit IpmiHandler(const oem::Handler& handler) : handler_(handler) {} 429 430 private: 431 oem::Handler handler_; 432 433 /** @brief call the registered handler with the request 434 * 435 * This is called from the running queue context after it has already 436 * created a request object that contains all the information required to 437 * execute the ipmi command. This function will return the response object 438 * pointer that owns the response object that will ultimately get sent back 439 * to the requester. 440 * 441 * Because this is the legacy variety of IPMI handler, this function does 442 * not really have to do much other than pass the payload to the callback 443 * and return response to the caller. 444 * 445 * @param request a shared_ptr to a Request object 446 * 447 * @return a shared_ptr to a Response object 448 */ 449 message::Response::ptr 450 executeCallback(message::Request::ptr request) override 451 { 452 message::Response::ptr response = request->makeResponse(); 453 // allocate a big response buffer here 454 response->payload.resize(maxLegacyBufferSize); 455 456 size_t len = request->payload.size() - request->payload.rawIndex; 457 Cc ccRet{ccSuccess}; 458 try 459 { 460 ccRet = 461 handler_(request->ctx->cmd, 462 request->payload.data() + request->payload.rawIndex, 463 response->payload.data(), &len); 464 } 465 catch (const HandlerException& e) 466 { 467 phosphor::logging::log<phosphor::logging::level::INFO>( 468 "Legacy OEM Handler produced exception", 469 phosphor::logging::entry("CC=%x", e.code()), 470 phosphor::logging::entry("EXCEPTION=%s", e.what()), 471 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 472 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 473 return errorResponse(request, e.code()); 474 } 475 catch (const std::exception& e) 476 { 477 phosphor::logging::log<phosphor::logging::level::ERR>( 478 "Legacy OEM Handler failed to catch exception", 479 phosphor::logging::entry("EXCEPTION=%s", e.what()), 480 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 481 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 482 return errorResponse(request, ccUnspecifiedError); 483 } 484 catch (const HandlerCompletion& c) 485 { 486 return errorResponse(request, c.code()); 487 } 488 catch (...) 489 { 490 const char* what = currentExceptionType(); 491 phosphor::logging::log<phosphor::logging::level::ERR>( 492 "Handler failed to catch exception", 493 phosphor::logging::entry("EXCEPTION=%s", what), 494 phosphor::logging::entry("NETFN=%x", request->ctx->netFn), 495 phosphor::logging::entry("CMD=%x", request->ctx->cmd)); 496 return errorResponse(request, ccUnspecifiedError); 497 } 498 response->cc = ccRet; 499 response->payload.resize(len); 500 return response; 501 } 502 }; 503 504 /** 505 * @brief create a legacy IPMI handler class and return a shared_ptr 506 * 507 * The queue uses a map of pointers to do the lookup. This function returns the 508 * shared_ptr that owns the Handler object. 509 * 510 * This is called internally via the ipmi_register_callback function. 511 * 512 * @param handler the function pointer to the callback 513 * 514 * @return A shared_ptr to the created handler object 515 */ 516 inline auto makeLegacyHandler(const ipmid_callback_t& handler, 517 void* ctx = nullptr) 518 { 519 HandlerBase::ptr ptr(new IpmiHandler<ipmid_callback_t>(handler, ctx)); 520 return ptr; 521 } 522 523 /** 524 * @brief create a legacy IPMI OEM handler class and return a shared_ptr 525 * 526 * The queue uses a map of pointers to do the lookup. This function returns the 527 * shared_ptr that owns the Handler object. 528 * 529 * This is called internally via the Router::registerHandler method. 530 * 531 * @param handler the function pointer to the callback 532 * 533 * @return A shared_ptr to the created handler object 534 */ 535 inline auto makeLegacyHandler(oem::Handler&& handler) 536 { 537 HandlerBase::ptr ptr( 538 new IpmiHandler<oem::Handler>(std::forward<oem::Handler>(handler))); 539 return ptr; 540 } 541 #endif // ALLOW_DEPRECATED_API 542 543 /** 544 * @brief create an IPMI handler class and return a shared_ptr 545 * 546 * The queue uses a map of pointers to do the lookup. This function returns the 547 * shared_ptr that owns the Handler object. 548 * 549 * This is called internally via the ipmi::registerHandler function. 550 * 551 * @param handler the function pointer to the callback 552 * 553 * @return A shared_ptr to the created handler object 554 */ 555 template <typename Handler> 556 inline auto makeHandler(Handler&& handler) 557 { 558 HandlerBase::ptr ptr( 559 new IpmiHandler<Handler>(std::forward<Handler>(handler))); 560 return ptr; 561 } 562 563 namespace impl 564 { 565 566 // IPMI command handler registration implementation 567 bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv, 568 ::ipmi::HandlerBase::ptr handler); 569 bool registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv, 570 ::ipmi::HandlerBase::ptr handler); 571 bool registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv, 572 ::ipmi::HandlerBase::ptr handler); 573 574 } // namespace impl 575 576 /** 577 * @brief main IPMI handler registration function 578 * 579 * This function should be used to register all new-style IPMI handler 580 * functions. This function just passes the callback to makeHandler, which 581 * creates a new wrapper object that will automatically extract the appropriate 582 * parameters for the callback function as well as pack up the response. 583 * 584 * @param prio - priority at which to register; see api.hpp 585 * @param netFn - the IPMI net function number to register 586 * @param cmd - the IPMI command number to register 587 * @param priv - the IPMI user privilige required for this command 588 * @param handler - the callback function that will handle this request 589 * 590 * @return bool - success of registering the handler 591 */ 592 template <typename Handler> 593 bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv, 594 Handler&& handler) 595 { 596 auto h = ipmi::makeHandler(std::forward<Handler>(handler)); 597 return impl::registerHandler(prio, netFn, cmd, priv, h); 598 } 599 600 /** 601 * @brief register a IPMI OEM group handler 602 * 603 * From IPMI 2.0 spec Network Function Codes Table (Row 2Ch): 604 * The first data byte position in requests and responses under this network 605 * function identifies the defining body that specifies command functionality. 606 * Software assumes that the command and completion code field positions will 607 * hold command and completion code values. 608 * 609 * The following values are used to identify the defining body: 610 * 00h PICMG - PCI Industrial Computer Manufacturer’s Group. (www.picmg.com) 611 * 01h DMTF Pre-OS Working Group ASF Specification (www.dmtf.org) 612 * 02h Server System Infrastructure (SSI) Forum (www.ssiforum.org) 613 * 03h VITA Standards Organization (VSO) (www.vita.com) 614 * DCh DCMI Specifications (www.intel.com/go/dcmi) 615 * all other Reserved 616 * 617 * When this network function is used, the ID for the defining body occupies 618 * the first data byte in a request, and the second data byte (following the 619 * completion code) in a response. 620 * 621 * @tparam Handler - implicitly specified callback function type 622 * @param prio - priority at which to register; see api.hpp 623 * @param netFn - the IPMI net function number to register 624 * @param cmd - the IPMI command number to register 625 * @param priv - the IPMI user privilige required for this command 626 * @param handler - the callback function that will handle this request 627 * 628 * @return bool - success of registering the handler 629 * 630 */ 631 template <typename Handler> 632 void registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv, 633 Handler&& handler) 634 { 635 auto h = ipmi::makeHandler(handler); 636 impl::registerGroupHandler(prio, group, cmd, priv, h); 637 } 638 639 /** 640 * @brief register a IPMI OEM IANA handler 641 * 642 * From IPMI spec Network Function Codes Table (Row 2Eh): 643 * The first three data bytes of requests and responses under this network 644 * function explicitly identify the OEM or non-IPMI group that specifies the 645 * command functionality. While the OEM or non-IPMI group defines the 646 * functional semantics for the cmd and remaining data fields, the cmd field 647 * is required to hold the same value in requests and responses for a given 648 * operation in order to be supported under the IPMI message handling and 649 * transport mechanisms. 650 * 651 * When this network function is used, the IANA Enterprise Number for the 652 * defining body occupies the first three data bytes in a request, and the 653 * first three data bytes following the completion code position in a 654 * response. 655 * 656 * @tparam Handler - implicitly specified callback function type 657 * @param prio - priority at which to register; see api.hpp 658 * @param netFn - the IPMI net function number to register 659 * @param cmd - the IPMI command number to register 660 * @param priv - the IPMI user privilige required for this command 661 * @param handler - the callback function that will handle this request 662 * 663 * @return bool - success of registering the handler 664 * 665 */ 666 template <typename Handler> 667 void registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv, 668 Handler&& handler) 669 { 670 auto h = ipmi::makeHandler(handler); 671 impl::registerOemHandler(prio, iana, cmd, priv, h); 672 } 673 674 } // namespace ipmi 675 676 #ifdef ALLOW_DEPRECATED_API 677 /** 678 * @brief legacy IPMI handler registration function 679 * 680 * This function should be used to register all legacy IPMI handler 681 * functions. This function just behaves just as the legacy registration 682 * mechanism did, silently replacing any existing handler with a new one. 683 * 684 * @param netFn - the IPMI net function number to register 685 * @param cmd - the IPMI command number to register 686 * @param context - ignored 687 * @param handler - the callback function that will handle this request 688 * @param priv - the IPMI user privilige required for this command 689 */ 690 // [[deprecated("Use ipmi::registerHandler() instead")]] 691 void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd, 692 ipmi_context_t context, ipmid_callback_t handler, 693 ipmi_cmd_privilege_t priv); 694 695 #endif /* ALLOW_DEPRECATED_API */ 696