LCOV - code coverage report
Current view: top level - xenolith/resources/network - XLNetworkController.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 119 181 65.7 %
Date: 2024-05-12 00:16:13 Functions: 26 37 70.3 %

          Line data    Source code
       1             : /**
       2             :  Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
       3             : 
       4             :  Permission is hereby granted, free of charge, to any person obtaining a copy
       5             :  of this software and associated documentation files (the "Software"), to deal
       6             :  in the Software without restriction, including without limitation the rights
       7             :  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       8             :  copies of the Software, and to permit persons to whom the Software is
       9             :  furnished to do so, subject to the following conditions:
      10             : 
      11             :  The above copyright notice and this permission notice shall be included in
      12             :  all copies or substantial portions of the Software.
      13             : 
      14             :  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      15             :  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      16             :  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      17             :  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      18             :  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      19             :  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
      20             :  THE SOFTWARE.
      21             :  **/
      22             : 
      23             : #include "XLNetworkController.h"
      24             : #include "SPNetworkContext.h"
      25             : #include "XLNetworkRequest.h"
      26             : 
      27             : namespace STAPPLER_VERSIONIZED stappler::xenolith::network {
      28             : 
      29             : struct ControllerHandle {
      30             :         using Context = stappler::network::Context<Interface>;
      31             : 
      32             :         Rc<Request> request;
      33             :         Handle *handle;
      34             :         Context context;
      35             : };
      36             : 
      37             : struct Controller::Data final : thread::ThreadInterface<Interface> {
      38             :         using Context = stappler::network::Context<Interface>;
      39             : 
      40             :         Application *_application = nullptr;
      41             :         Controller *_controller = nullptr;
      42             :         String _name;
      43             :         Bytes _signKey;
      44             : 
      45             :         std::thread _thread;
      46             : 
      47             :         Mutex _mutexQueue;
      48             :         Mutex _mutexFree;
      49             : 
      50             :         CURLM *_handle = nullptr;
      51             : 
      52             :         memory::PriorityQueue<Rc<Request>> _pending;
      53             : 
      54             :         std::atomic_flag _shouldQuit;
      55             :         Map<String, void *> _sharegroups;
      56             : 
      57             :         Map<CURL *, ControllerHandle> _handles;
      58             :         NetworkCapabilities _capabilities = NetworkCapabilities::None;
      59             : 
      60             :         Data(Application *app, Controller *c, StringView name, Bytes &&signKey);
      61             :         virtual ~Data();
      62             : 
      63             :         bool init();
      64             :         void invalidate();
      65             : 
      66             :         virtual void threadInit() override;
      67             :         virtual bool worker() override;
      68             :         virtual void threadDispose() override;
      69             : 
      70             :         void *getSharegroup(StringView);
      71             : 
      72             :         void onUploadProgress(Handle *, int64_t total, int64_t now);
      73             :         void onDownloadProgress(Handle *, int64_t total, int64_t now);
      74             :         bool onComplete(Handle *, bool success);
      75             : 
      76             :         void sign(NetworkHandle &, Context &) const;
      77             : 
      78             :         void pushTask(Rc<Request> &&handle);
      79             :         void wakeup();
      80             : 
      81             :         bool prepare(Handle &handle, Context *ctx, const Callback<bool(CURL *)> &onBeforePerform);
      82             :         bool finalize(Handle &handle, Context *ctx, const Callback<bool(CURL *)> &onAfterPerform);
      83             : };
      84             : 
      85             : SPUNUSED static void registerNetworkCallback(Application *, void *, Function<void(NetworkCapabilities)> &&);
      86             : SPUNUSED static void unregisterNetworkCallback(Application *, void *);
      87             : 
      88             : XL_DECLARE_EVENT(Controller, "network::Controller", onNetworkCapabilities);
      89             : 
      90          21 : Controller::Data::Data(Application *app, Controller *c, StringView name, Bytes &&signKey)
      91          21 : : _application(app), _controller(c), _name(name.str<Interface>()), _signKey(move(signKey)) {
      92             : 
      93          21 : }
      94             : 
      95          42 : Controller::Data::~Data() {
      96          42 : }
      97             : 
      98          21 : bool Controller::Data::init() {
      99          21 :         registerNetworkCallback(_application, this, [this] (NetworkCapabilities cap) {
     100          21 :                 _application->performOnMainThread([this, cap] {
     101          21 :                         _capabilities = cap;
     102          21 :                         Controller::onNetworkCapabilities(_controller, int64_t(toInt(_capabilities)));
     103          21 :                 }, this);
     104          21 :         });
     105          21 :         _thread = std::thread(Controller::Data::workerThread, this, nullptr);
     106          21 :         return true;
     107             : }
     108             : 
     109           0 : void Controller::Data::invalidate() {
     110           0 :         unregisterNetworkCallback(_application, this);
     111           0 : }
     112             : 
     113          21 : void Controller::Data::threadInit() {
     114          21 :         _shouldQuit.test_and_set();
     115          21 :         _pending.setQueueLocking(_mutexQueue);
     116          21 :         _pending.setFreeLocking(_mutexFree);
     117             : 
     118          21 :         thread::ThreadInfo::setThreadInfo(_name);
     119             : 
     120          21 :         _handle = curl_multi_init();
     121          21 : }
     122             : 
     123        1382 : bool Controller::Data::worker() {
     124        2764 :         if (!_shouldQuit.test_and_set()) {
     125          21 :                 return false;
     126             :         }
     127             : 
     128             :         do {
     129        1361 :                 if (!_pending.pop_direct([&, this] (memory::PriorityQueue<Rc<Handle>>::PriorityType type, Rc<Request> &&it) {
     130          21 :                         auto h = curl_easy_init();
     131          21 :                         auto networkHandle = const_cast<Handle *>(&it->getHandle());
     132          21 :                         auto i = _handles.emplace(h, ControllerHandle{move(it), networkHandle}).first;
     133             : 
     134          21 :                         auto sg = i->second.handle->getSharegroup();
     135          21 :                         if (!sg.empty()) {
     136           0 :                                 i->second.context.share = getSharegroup(sg);
     137             :                         }
     138             : 
     139          21 :                         i->second.context.userdata = this;
     140          21 :                         i->second.context.curl = h;
     141          21 :                         i->second.context.origHandle = networkHandle;
     142             : 
     143          21 :                         i->second.context.origHandle->setDownloadProgress([this, h = networkHandle] (int64_t total, int64_t now) -> int {
     144          21 :                                 onDownloadProgress(h, total, now);
     145          21 :                                 return 0;
     146             :                         });
     147             : 
     148          21 :                         i->second.context.origHandle->setUploadProgress([this,  h = networkHandle] (int64_t total, int64_t now) -> int {
     149           0 :                                 onUploadProgress(h, total, now);
     150           0 :                                 return 0;
     151             :                         });
     152             : 
     153          21 :                         if (i->second.handle->shouldSignRequest()) {
     154           0 :                                 sign(*networkHandle, i->second.context);
     155             :                         }
     156             : 
     157          21 :                         prepare(*networkHandle, &i->second.context, nullptr);
     158             : 
     159          21 :                         curl_multi_add_handle(reinterpret_cast<CURLM *>(_handle), h);
     160          21 :                 })) {
     161        1340 :                         break;
     162             :                 }
     163             :         } while (0);
     164             : 
     165        1361 :         int running = 0;
     166        1361 :         auto err = curl_multi_perform(reinterpret_cast<CURLM *>(_handle), &running);
     167        1361 :         if (err != CURLM_OK) {
     168           0 :                 log::error("CURL", toString("Fail to perform multi: ", err));
     169           0 :                 return false;
     170             :         }
     171             : 
     172        1361 :         int timeout = 16;
     173        1361 :         if (running == 0) {
     174         193 :                 timeout = 1000;
     175             :         }
     176             : 
     177        1361 :         err = curl_multi_poll(reinterpret_cast<CURLM *>(_handle), NULL, 0, timeout, nullptr);
     178        1361 :         if (err != CURLM_OK) {
     179           0 :                 log::error("CURL", toString("Fail to poll multi: ", err));
     180           0 :                 return false;
     181             :         }
     182             : 
     183        1361 :         struct CURLMsg *msg = nullptr;
     184             :         do {
     185        1382 :                 int msgq = 0;
     186        1382 :                 msg = curl_multi_info_read(reinterpret_cast<CURLM *>(_handle), &msgq);
     187        1382 :                 if (msg && (msg->msg == CURLMSG_DONE)) {
     188          21 :                         CURL *e = msg->easy_handle;
     189          21 :                         curl_multi_remove_handle(reinterpret_cast<CURLM *>(_handle), e);
     190             : 
     191          21 :                         auto it = _handles.find(e);
     192          21 :                         if (it != _handles.end()) {
     193          21 :                                 it->second.context.code = msg->data.result;
     194          21 :                                 auto ret = finalize(*it->second.handle, &it->second.context, nullptr);
     195          21 :                                 if (!onComplete(it->second.handle, ret)) {
     196           0 :                                         _handles.erase(it);
     197           0 :                                         return false;
     198             :                                 }
     199          21 :                                 _handles.erase(it);
     200             :                         }
     201             : 
     202          21 :                         curl_easy_cleanup(e);
     203             :                 }
     204        1382 :         } while (msg);
     205             : 
     206        1361 :         return true;
     207             : }
     208             : 
     209          21 : void Controller::Data::threadDispose() {
     210          21 :         if (_handle) {
     211          21 :                 for (auto &it : _handles) {
     212           0 :                         curl_multi_remove_handle(reinterpret_cast<CURLM *>(_handle), it.first);
     213           0 :                         it.second.context.code = CURLE_FAILED_INIT;
     214           0 :                         finalize(*it.second.handle, &it.second.context, nullptr);
     215           0 :                         curl_easy_cleanup(it.first);
     216             :                 }
     217             : 
     218          21 :                 curl_multi_cleanup(reinterpret_cast<CURLM *>(_handle));
     219             : 
     220          21 :                 for (auto &it : _sharegroups) {
     221           0 :                         curl_share_cleanup((CURLSH *)it.second);
     222             :                 }
     223             : 
     224          21 :                 _handles.clear();
     225          21 :                 _sharegroups.clear();
     226             : 
     227          21 :                 _handle = nullptr;
     228             :         }
     229          21 : }
     230             : 
     231           0 : void *Controller::Data::getSharegroup(StringView name) {
     232           0 :         auto it = _sharegroups.find(name);
     233           0 :         if (it != _sharegroups.end()) {
     234           0 :                 return it->second;
     235             :         }
     236             : 
     237           0 :         auto sharegroup = curl_share_init();
     238           0 :         curl_share_setopt(reinterpret_cast<CURLSH *>(sharegroup), CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
     239           0 :         curl_share_setopt(reinterpret_cast<CURLSH *>(sharegroup), CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
     240           0 :         curl_share_setopt(reinterpret_cast<CURLSH *>(sharegroup), CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
     241             : 
     242           0 :         _sharegroups.emplace(name.str<Interface>(), sharegroup);
     243           0 :         return sharegroup;
     244             : }
     245             : 
     246           0 : void Controller::Data::onUploadProgress(Handle *handle, int64_t total, int64_t now) {
     247           0 :         _application->performOnMainThread([handle, total, now] {
     248           0 :                 auto req = handle->getReqeust();
     249           0 :                 req->notifyOnUploadProgress(total, now);
     250           0 :         });
     251           0 :         _application->wakeup();
     252           0 : }
     253             : 
     254          21 : void Controller::Data::onDownloadProgress(Handle *handle, int64_t total, int64_t now) {
     255          21 :         _application->performOnMainThread([handle, total, now] {
     256          21 :                 auto req = handle->getReqeust();
     257          21 :                 req->notifyOnDownloadProgress(total, now);
     258          21 :         });
     259          21 :         _application->wakeup();
     260          21 : }
     261             : 
     262          21 : bool Controller::Data::onComplete(Handle *handle, bool success) {
     263          21 :         _application->performOnMainThread([handle, success] {
     264          21 :                 auto req = handle->getReqeust();
     265          21 :                 req->notifyOnComplete(success);
     266          21 :         });
     267             : 
     268          21 :         _application->wakeup();
     269          21 :         return true;
     270             : }
     271             : 
     272           0 : void Controller::Data::sign(NetworkHandle &handle, Context &ctx) const {
     273           0 :         String date = Time::now().toHttp<Interface>();
     274           0 :         StringStream message;
     275             : 
     276           0 :         message << handle.getUrl() << "\r\n";
     277           0 :         message << "X-ApplicationName: " << _application->getInfo().bundleName << "\r\n";
     278           0 :         message << "X-ApplicationVersion: " << _application->getInfo().applicationVersionCode << "\r\n";
     279           0 :         message << "X-ClientDate: " << date << "\r\n";
     280           0 :         message << "User-Agent: " << _application->getInfo().userAgent << "\r\n";
     281             : 
     282           0 :         auto msg = message.str();
     283           0 :         auto sig = string::Sha512::hmac(msg, _signKey);
     284             : 
     285           0 :         ctx.headers = curl_slist_append(ctx.headers, toString("X-ClientDate: ", date).data());
     286           0 :         ctx.headers = curl_slist_append(ctx.headers, toString("X-Stappler-Sign: ", base64url::encode<Interface>(sig)).data());
     287             : 
     288           0 :         if (!_application->getInfo().userAgent.empty()) {
     289           0 :                 handle.setUserAgent(_application->getInfo().userAgent);
     290             :         }
     291           0 : }
     292             : 
     293          21 : void Controller::Data::pushTask(Rc<Request> &&handle) {
     294          21 :         _pending.push(0, false, move(handle));
     295          21 :         curl_multi_wakeup(_handle);
     296          21 : }
     297             : 
     298           0 : void Controller::Data::wakeup() {
     299           0 :         curl_multi_wakeup(_handle);
     300           0 : }
     301             : 
     302          21 : bool Controller::Data::prepare(Handle &handle, Context *ctx, const Callback<bool(CURL *)> &onBeforePerform) {
     303          21 :         if (!handle.prepare(ctx)) {
     304           0 :                 return false;
     305             :         }
     306             : 
     307          21 :         return stappler::network::prepare(*handle.getData(), ctx, onBeforePerform);
     308             : }
     309             : 
     310          21 : bool Controller::Data::finalize(Handle &handle, Context *ctx, const Callback<bool(CURL *)> &onAfterPerform) {
     311          21 :         auto ret = stappler::network::finalize(*handle.getData(), ctx, onAfterPerform);
     312          21 :         return handle.finalize(ctx, ret);
     313             : }
     314             : 
     315          21 : Controller::Controller(Application *app, StringView name, Bytes &&signKey) {
     316          21 :         _data = new Data(app, this, name, move(signKey));
     317          21 :         _data->init();
     318          21 : }
     319             : 
     320          42 : Controller::~Controller() {
     321          21 :         _data->_shouldQuit.clear();
     322          21 :         curl_multi_wakeup(_data->_handle);
     323          21 :         _data->_thread.join();
     324          21 :         delete _data;
     325          42 : }
     326             : 
     327          21 : void Controller::initialize(Application *) {
     328             : 
     329          21 : }
     330             : 
     331          21 : void Controller::invalidate(Application *) {
     332             : 
     333          21 : }
     334             : 
     335        3846 : void Controller::update(Application *, const UpdateTime &t) {
     336             : 
     337        3846 : }
     338             : 
     339          42 : Application *Controller::getApplication() const {
     340          42 :         return _data->_application;
     341             : }
     342             : 
     343           0 : StringView Controller::getName() const {
     344           0 :         return _data->_name;
     345             : }
     346             : 
     347          21 : void Controller::run(Rc<Request> &&handle) {
     348          21 :         _data->pushTask(move(handle));
     349          21 : }
     350             : 
     351           0 : void Controller::setSignKey(Bytes &&value) {
     352             : 
     353           0 : }
     354             : 
     355           0 : bool Controller::isNetworkOnline() const {
     356           0 :         return (_data->_capabilities & NetworkCapabilities::Internet) != NetworkCapabilities::None;
     357             : }
     358             : 
     359           0 : NetworkCapabilities Controller::getNetworkCapabilities() const {
     360           0 :         return _data->_capabilities;
     361             : }
     362             : 
     363             : 
     364             : }

Generated by: LCOV version 1.14