You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

375 lines
8.6 KiB

  1. #ifndef _UBUS_CXX_HPP
  2. #define _UBUS_CXX_HPP
  3. #include <cassert>
  4. #include <libubus.h>
  5. #include <memory>
  6. #include <mutex>
  7. #include <string>
  8. #include <utility>
  9. #include <vector>
  10. #ifndef NDEBUG
  11. #include <iostream>
  12. #endif
  13. namespace ubus {
  14. static constexpr int call_timeout = 500;
  15. using msg_ptr = std::shared_ptr<const blob_attr>;
  16. using strings = std::vector<std::string>;
  17. inline auto concat(strings dest) { return dest; }
  18. template<class ...Strings>
  19. inline auto concat(strings dest, strings src, Strings ...more)
  20. {
  21. dest.reserve(dest.size() + src.size());
  22. dest.insert(std::end(dest), std::make_move_iterator(std::begin(src)),
  23. std::make_move_iterator(std::end(src)));
  24. return concat(std::move(dest), std::move(more)...);
  25. }
  26. template<class S, class ...Strings>
  27. inline auto concat(strings dest, S src, Strings ...more)
  28. {
  29. dest.emplace_back(std::move(src));
  30. return concat(std::move(dest), std::move(more)...);
  31. }
  32. class iterator {
  33. private:
  34. const strings & keys;
  35. const size_t n = 0;
  36. size_t i = 0;
  37. const blob_attr * pos = nullptr;
  38. std::unique_ptr<iterator> cur{};
  39. iterator * parent = nullptr;
  40. size_t rem = 0;
  41. [[nodiscard]] inline auto matches() const -> bool
  42. {
  43. return (keys[i].empty() || blobmsg_name(cur->pos)==keys[i]);
  44. }
  45. explicit iterator(iterator * par)
  46. : keys{par->keys}, n{par->n}, pos{par->pos}, cur{this}, parent{par}
  47. {
  48. if (pos!=nullptr) {
  49. rem = blobmsg_data_len(pos);
  50. pos = static_cast<blob_attr *>(blobmsg_data(pos));
  51. }
  52. }
  53. public:
  54. explicit iterator(const blob_attr * msg, const strings & filter={""})
  55. : keys{filter}, n{keys.size()-1}, pos{msg}, cur{this}
  56. {
  57. if (pos!=nullptr) {
  58. rem = blobmsg_data_len(pos);
  59. pos = static_cast<blob_attr *>(blobmsg_data(pos));
  60. if (rem==0) { pos = nullptr; }
  61. else if (i!=n || !matches()) { ++*this; }
  62. }
  63. }
  64. inline iterator(iterator &&) noexcept = default;
  65. inline iterator(const iterator &) = delete;
  66. inline auto operator=(const iterator &) -> iterator & = delete;
  67. inline auto operator=(iterator &&) -> iterator & = delete;
  68. inline auto operator*() { return cur->pos; }
  69. inline auto operator!=(const iterator & rhs)
  70. { return (cur->rem!=rhs.cur->rem || cur->pos!=rhs.cur->pos); }
  71. auto operator++() -> iterator &;
  72. inline ~iterator()
  73. { if (cur.get()==this) { static_cast<void>(cur.release()); } }
  74. };
  75. class message {
  76. private:
  77. const msg_ptr msg{}; // initialized by callback.
  78. const strings keys{};
  79. public:
  80. inline explicit message(msg_ptr message_ptr, strings filter={""})
  81. : msg{std::move(message_ptr)}, keys{std::move(filter)} {}
  82. inline message(message &&) = default;
  83. inline message(const message &) = delete;
  84. inline auto operator=(message &&) -> message & = delete;
  85. inline auto operator=(const message &) -> message & = delete;
  86. [[nodiscard]] inline auto begin() const -> iterator
  87. { return iterator{msg.get(), keys}; }
  88. [[nodiscard]] inline auto end() const -> iterator
  89. { return iterator{nullptr, keys}; }
  90. inline explicit operator bool() const { return begin()!=end(); }
  91. template<class ...Strings>
  92. auto filter(Strings ...filter)
  93. {
  94. strings both{};
  95. if (keys.size()!=1 || !keys[0].empty()) { both = keys; }
  96. both = concat(std::move(both), std::move(filter)...);
  97. return std::move(message{msg, std::move(both)});
  98. }
  99. inline ~message() = default;
  100. };
  101. class lock_shared_resources {
  102. private:
  103. static std::mutex inuse;
  104. public:
  105. inline lock_shared_resources() { inuse.lock(); }
  106. inline lock_shared_resources(lock_shared_resources &&) noexcept = default;
  107. inline lock_shared_resources(const lock_shared_resources &) = delete;
  108. inline auto operator=(const lock_shared_resources &) -> auto & = delete;
  109. inline auto operator=(lock_shared_resources &&) -> auto && = delete;
  110. //NOLINTNEXTLINE(readability-convert-member-functions-to-static)
  111. inline auto get_context() -> ubus_context * // is member to enforce inuse.
  112. {
  113. static auto ubus_freeing = [] (ubus_context * ctx) { ubus_free(ctx); };
  114. static std::unique_ptr<ubus_context, decltype(ubus_freeing)>
  115. lazy_ctx{ubus_connect(nullptr), ubus_freeing};
  116. if (!lazy_ctx) { // it could be available on a later call:
  117. lazy_ctx.reset(ubus_connect(nullptr));
  118. if (!lazy_ctx) {
  119. throw std::runtime_error("ubus error: cannot connect context");
  120. }
  121. }
  122. return lazy_ctx.get();
  123. }
  124. //NOLINTNEXTLINE(readability-convert-member-functions-to-static)
  125. inline auto get_blob_buf() -> blob_buf * // is member to enforce inuse.
  126. {
  127. static blob_buf buf;
  128. static auto blob_buf_freeing = [] (blob_buf * b) { blob_buf_free(b); };
  129. static std::unique_ptr<blob_buf, decltype(blob_buf_freeing)>
  130. created_to_free_on_the_end_of_life{&buf, blob_buf_freeing};
  131. blob_buf_init(&buf, 0);
  132. return &buf;
  133. }
  134. inline ~lock_shared_resources() { inuse.unlock(); }
  135. };
  136. template<class F>
  137. auto call(const char * path, const char * method, F set_arguments,
  138. int timeout=call_timeout) -> message;
  139. inline auto call(const char * path, const char * method,
  140. int timeout=call_timeout) -> message
  141. { return call(path, method, [](blob_buf * /*buf*/) { return 0; }, timeout); }
  142. inline auto call(const char * path, int timeout=call_timeout) -> message
  143. { return call(path, "", timeout); }
  144. // ------------------------- implementation: ----------------------------------
  145. std::mutex lock_shared_resources::inuse;
  146. inline auto iterator::operator++() -> iterator &
  147. {
  148. for(;;) {
  149. #ifndef NDEBUG
  150. std::cout<<std::string(i,'>')<<" look for "<<keys[i]<<" at ";
  151. std::cout<<blobmsg_name(cur->pos)<<std::endl;
  152. #endif
  153. auto id = blob_id(cur->pos);
  154. if ( (id==BLOBMSG_TYPE_TABLE || id==BLOBMSG_TYPE_ARRAY)
  155. && i<n
  156. && matches()
  157. && blobmsg_data_len(cur->pos)>0 )
  158. { //immmerge:
  159. ++i;
  160. auto tmp = cur.release();
  161. struct new_iterator : public iterator // use private constructor:
  162. { explicit new_iterator(iterator * par) : iterator{par} {} };
  163. cur = std::make_unique<new_iterator>(tmp);
  164. } else {
  165. while (true) {
  166. cur->rem -= blob_pad_len(cur->pos);
  167. cur->pos = blob_next(cur->pos);
  168. auto len = blob_pad_len(cur->pos);
  169. if (cur->rem>0 && len<=cur->rem && len>=sizeof(blob_attr))
  170. { break; }
  171. //emerge:
  172. auto tmp = cur->parent;
  173. if (tmp == nullptr) {
  174. cur->pos = nullptr;
  175. return *cur;
  176. }
  177. cur.reset(tmp);
  178. --i;
  179. }
  180. }
  181. if (i==n && matches()) { return *cur; }
  182. }
  183. }
  184. template<class F>
  185. inline auto call(const char * path, const char * method, F set_arguments,
  186. int timeout) -> message
  187. {
  188. auto shared = lock_shared_resources{};
  189. auto ctx = shared.get_context();
  190. uint32_t id;
  191. int err = ubus_lookup_id(ctx, path, &id);
  192. if (err==0) { // call
  193. ubus_request request{};
  194. auto buf = shared.get_blob_buf();
  195. err = set_arguments(buf);
  196. if (err==0) {
  197. err = ubus_invoke_async(ctx, id, method, buf->head, &request);
  198. }
  199. if (err==0) {
  200. msg_ptr message_ptr;
  201. /* Cannot capture message_ptr, the lambda would be another type.
  202. * Pass a location where to save the message as priv pointer when
  203. * invoking and get it back here:
  204. */
  205. request.priv = &message_ptr;
  206. request.data_cb =
  207. [](ubus_request * req, int /*type*/, blob_attr * msg)
  208. {
  209. if (req==nullptr || msg==nullptr) { return; }
  210. auto saved = static_cast<msg_ptr *>(req->priv);
  211. if (saved==nullptr || *saved) { return; }
  212. saved->reset(blob_memdup(msg), free);
  213. if (!*saved) { throw std::bad_alloc(); }
  214. };
  215. err = ubus_complete_request(ctx, &request, timeout);
  216. if (err==0) { return message{message_ptr}; }
  217. }
  218. }
  219. std::string errmsg = "ubus::call error: cannot invoke";
  220. errmsg += " (" + std::to_string(err) + ") " + path + " " + method;
  221. throw std::runtime_error(errmsg);
  222. }
  223. } // namespace ubus
  224. #endif