#ifndef _UBUS_CXX_HPP #define _UBUS_CXX_HPP #include #include #include #include #include #include #include #ifndef NDEBUG #include #endif namespace ubus { static constexpr int call_timeout = 500; using msg_ptr = std::shared_ptr; using strings = std::vector; inline auto concat(strings dest) { return dest; } template inline auto concat(strings dest, strings src, Strings ...more) { dest.reserve(dest.size() + src.size()); dest.insert(std::end(dest), std::make_move_iterator(std::begin(src)), std::make_move_iterator(std::end(src))); return concat(std::move(dest), std::move(more)...); } template inline auto concat(strings dest, S src, Strings ...more) { dest.emplace_back(std::move(src)); return concat(std::move(dest), std::move(more)...); } class iterator { private: const strings & keys; const size_t n = 0; size_t i = 0; const blob_attr * pos = nullptr; std::unique_ptr cur{}; iterator * parent = nullptr; size_t rem = 0; [[nodiscard]] inline auto matches() const -> bool { return (keys[i].empty() || blobmsg_name(cur->pos)==keys[i]); } explicit iterator(iterator * par) : keys{par->keys}, n{par->n}, pos{par->pos}, cur{this}, parent{par} { if (pos!=nullptr) { rem = blobmsg_data_len(pos); pos = static_cast(blobmsg_data(pos)); } } public: explicit iterator(const blob_attr * msg, const strings & filter={""}) : keys{filter}, n{keys.size()-1}, pos{msg}, cur{this} { if (pos!=nullptr) { rem = blobmsg_data_len(pos); pos = static_cast(blobmsg_data(pos)); if (rem==0) { pos = nullptr; } else if (i!=n || !matches()) { ++*this; } } } inline iterator(iterator &&) noexcept = default; inline iterator(const iterator &) = delete; inline auto operator=(const iterator &) -> iterator & = delete; inline auto operator=(iterator &&) -> iterator & = delete; inline auto operator*() { return cur->pos; } inline auto operator!=(const iterator & rhs) { return (cur->rem!=rhs.cur->rem || cur->pos!=rhs.cur->pos); } auto operator++() -> iterator &; inline ~iterator() { if (cur.get()==this) { static_cast(cur.release()); } } }; class message { private: const msg_ptr msg{}; // initialized by callback. const strings keys{}; public: inline explicit message(msg_ptr message_ptr, strings filter={""}) : msg{std::move(message_ptr)}, keys{std::move(filter)} {} inline message(message &&) = default; inline message(const message &) = delete; inline auto operator=(message &&) -> message & = delete; inline auto operator=(const message &) -> message & = delete; [[nodiscard]] inline auto begin() const -> iterator { return iterator{msg.get(), keys}; } [[nodiscard]] inline auto end() const -> iterator { return iterator{nullptr, keys}; } inline explicit operator bool() const { return begin()!=end(); } template auto filter(Strings ...filter) { strings both{}; if (keys.size()!=1 || !keys[0].empty()) { both = keys; } both = concat(std::move(both), std::move(filter)...); return std::move(message{msg, std::move(both)}); } inline ~message() = default; }; class lock_shared_resources { private: static std::mutex inuse; public: inline lock_shared_resources() { inuse.lock(); } inline lock_shared_resources(lock_shared_resources &&) noexcept = default; inline lock_shared_resources(const lock_shared_resources &) = delete; inline auto operator=(const lock_shared_resources &) -> auto & = delete; inline auto operator=(lock_shared_resources &&) -> auto && = delete; //NOLINTNEXTLINE(readability-convert-member-functions-to-static) inline auto get_context() -> ubus_context * // is member to enforce inuse. { static auto ubus_freeing = [] (ubus_context * ctx) { ubus_free(ctx); }; static std::unique_ptr lazy_ctx{ubus_connect(nullptr), ubus_freeing}; if (!lazy_ctx) { // it could be available on a later call: lazy_ctx.reset(ubus_connect(nullptr)); if (!lazy_ctx) { throw std::runtime_error("ubus error: cannot connect context"); } } return lazy_ctx.get(); } //NOLINTNEXTLINE(readability-convert-member-functions-to-static) inline auto get_blob_buf() -> blob_buf * // is member to enforce inuse. { static blob_buf buf; static auto blob_buf_freeing = [] (blob_buf * b) { blob_buf_free(b); }; static std::unique_ptr created_to_free_on_the_end_of_life{&buf, blob_buf_freeing}; blob_buf_init(&buf, 0); return &buf; } inline ~lock_shared_resources() { inuse.unlock(); } }; template auto call(const char * path, const char * method, F set_arguments, int timeout=call_timeout) -> message; inline auto call(const char * path, const char * method, int timeout=call_timeout) -> message { return call(path, method, [](blob_buf * /*buf*/) { return 0; }, timeout); } inline auto call(const char * path, int timeout=call_timeout) -> message { return call(path, "", timeout); } // ------------------------- implementation: ---------------------------------- std::mutex lock_shared_resources::inuse; inline auto iterator::operator++() -> iterator & { for(;;) { #ifndef NDEBUG std::cout<')<<" look for "<pos)<pos); if ( (id==BLOBMSG_TYPE_TABLE || id==BLOBMSG_TYPE_ARRAY) && ipos)>0 ) { //immmerge: ++i; auto tmp = cur.release(); struct new_iterator : public iterator // use private constructor: { explicit new_iterator(iterator * par) : iterator{par} {} }; cur = std::make_unique(tmp); } else { while (true) { cur->rem -= blob_pad_len(cur->pos); cur->pos = blob_next(cur->pos); auto len = blob_pad_len(cur->pos); if (cur->rem>0 && len<=cur->rem && len>=sizeof(blob_attr)) { break; } //emerge: auto tmp = cur->parent; if (tmp == nullptr) { cur->pos = nullptr; return *cur; } cur.reset(tmp); --i; } } if (i==n && matches()) { return *cur; } } } template inline auto call(const char * path, const char * method, F set_arguments, int timeout) -> message { auto shared = lock_shared_resources{}; auto ctx = shared.get_context(); uint32_t id; int err = ubus_lookup_id(ctx, path, &id); if (err==0) { // call ubus_request request{}; auto buf = shared.get_blob_buf(); err = set_arguments(buf); if (err==0) { err = ubus_invoke_async(ctx, id, method, buf->head, &request); } if (err==0) { msg_ptr message_ptr; /* Cannot capture message_ptr, the lambda would be another type. * Pass a location where to save the message as priv pointer when * invoking and get it back here: */ request.priv = &message_ptr; request.data_cb = [](ubus_request * req, int /*type*/, blob_attr * msg) { if (req==nullptr || msg==nullptr) { return; } auto saved = static_cast(req->priv); if (saved==nullptr || *saved) { return; } saved->reset(blob_memdup(msg), free); if (!*saved) { throw std::bad_alloc(); } }; err = ubus_complete_request(ctx, &request, timeout); if (err==0) { return message{message_ptr}; } } } std::string errmsg = "ubus::call error: cannot invoke"; errmsg += " (" + std::to_string(err) + ") " + path + " " + method; throw std::runtime_error(errmsg); } } // namespace ubus #endif