2 *------------------------------------------------------------------
3 * Copyright (c) 2017 Cisco and/or its affiliates.
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:
8 * http://www.apache.org/licenses/LICENSE-2.0
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 *------------------------------------------------------------------
18 #ifndef vapi_hpp_included
19 #define vapi_hpp_included
29 #include <vppinfra/types.h>
30 #include <vapi/vapi.h>
31 #include <vapi/vapi_internal.h>
32 #include <vapi/vapi_dbg.h>
33 #include <vapi/vpe.api.vapi.h>
35 #if VAPI_CPP_DEBUG_LEAKS
36 #include <unordered_set>
49 template <typename Req, typename Resp, typename... Args> class Request;
50 template <typename M> class Msg;
51 template <typename M> void vapi_swap_to_be (M *msg);
52 template <typename M> void vapi_swap_to_host (M *msg);
53 template <typename M, typename... Args>
54 M *vapi_alloc (Connection &con, Args...);
55 template <typename M> vapi_msg_id_t vapi_get_msg_id_t ();
56 template <typename M> class Event_registration;
58 class Unexpected_msg_id_exception : public std::exception
61 virtual const char *what () const throw ()
63 return "unexpected message id";
67 class Msg_not_available_exception : public std::exception
70 virtual const char *what () const throw ()
72 return "message unavailable";
77 /** response not ready yet */
80 /** response to request is ready */
83 /** no response to request (will never come) */
85 } vapi_response_state_e;
88 * Class representing common functionality of a request - response state
94 virtual ~Common_req (){};
96 Connection &get_connection ()
101 vapi_response_state_e get_response_state (void) const
103 return response_state;
108 Common_req (Connection &con) : con (con), response_state{RESPONSE_NOT_READY}
112 void set_response_state (vapi_response_state_e state)
114 response_state = state;
117 virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
120 void set_context (u32 context)
122 this->context = context;
131 vapi_response_state_e response_state;
133 friend class Connection;
135 template <typename M> friend class Msg;
137 template <typename Req, typename Resp, typename... Args>
138 friend class Request;
140 template <typename Req, typename Resp, typename... Args> friend class Dump;
142 template <typename M> friend class Event_registration;
146 * Class representing a connection to VPP
148 * After creating a Connection object, call connect() to actually connect
149 * to VPP. Use is_msg_available to discover whether a specific message is known
150 * and supported by the VPP connected to.
155 Connection (void) : vapi_ctx{0}, event_count{0}
158 vapi_error_e rv = VAPI_OK;
161 if (VAPI_OK != (rv = vapi_ctx_alloc (&vapi_ctx)))
163 throw std::bad_alloc ();
166 events.reserve (vapi_get_message_count () + 1);
169 Connection (const Connection &) = delete;
173 vapi_ctx_free (vapi_ctx);
174 #if VAPI_CPP_DEBUG_LEAKS
175 for (auto x : shm_data_set)
177 printf ("Leaked shm_data@%p!\n", x);
183 * @brief check if message identified by it's message id is known by the
184 * vpp to which the connection is open
186 bool is_msg_available (vapi_msg_id_t type)
188 return vapi_is_msg_available (vapi_ctx, type);
192 * @brief connect to vpp
194 * @param name application name
195 * @param chroot_prefix shared memory prefix
196 * @param max_queued_request max number of outstanding requests queued
198 * @return VAPI_OK on success, other error code on error
200 vapi_error_e connect (const char *name, const char *chroot_prefix,
201 int max_outstanding_requests, int response_queue_size)
203 return vapi_connect (vapi_ctx, name, chroot_prefix,
204 max_outstanding_requests, response_queue_size,
209 * @brief disconnect from vpp
211 * @return VAPI_OK on success, other error code on error
213 vapi_error_e disconnect ()
215 auto x = requests.size ();
218 VAPI_DBG ("popping request @%p", requests.front ());
219 requests.pop_front ();
222 return vapi_disconnect (vapi_ctx);
226 * @brief get event file descriptor
228 * @note this file descriptor becomes readable when messages (from vpp)
229 * are waiting in queue
231 * @param[out] fd pointer to result variable
233 * @return VAPI_OK on success, other error code on error
235 vapi_error_e get_fd (int *fd)
237 return vapi_get_fd (vapi_ctx, fd);
241 * @brief wait for responses from vpp and assign them to appropriate objects
243 * @param limit stop dispatch after the limit object received it's response
245 * @return VAPI_OK on success, other error code on error
247 vapi_error_e dispatch (const Common_req *limit = nullptr)
249 std::lock_guard<std::mutex> lock (dispatch_mutex);
250 vapi_error_e rv = VAPI_OK;
251 bool loop_again = true;
255 size_t shm_data_size;
256 rv = vapi_recv (vapi_ctx, &shm_data, &shm_data_size);
261 #if VAPI_CPP_DEBUG_LEAKS
262 on_shm_data_alloc (shm_data);
264 std::lock_guard<std::recursive_mutex> requests_lock (requests_mutex);
265 std::lock_guard<std::recursive_mutex> events_lock (events_mutex);
266 vapi_msg_id_t id = vapi_lookup_vapi_msg_id_t (
267 vapi_ctx, be16toh (*static_cast<u16 *> (shm_data)));
268 bool has_context = vapi_msg_is_with_context (id);
269 bool break_dispatch = false;
270 Common_req *matching_req = nullptr;
273 u32 context = *reinterpret_cast<u32 *> (
274 (static_cast<u8 *> (shm_data) + vapi_get_context_offset (id)));
275 const auto x = requests.front ();
277 if (context == x->context)
279 std::tie (rv, break_dispatch) =
280 x->assign_response (id, shm_data);
284 std::tie (rv, break_dispatch) =
285 x->assign_response (id, nullptr);
289 requests.pop_front ();
296 std::tie (rv, break_dispatch) =
297 events[id]->assign_response (id, shm_data);
298 matching_req = events[id];
305 if ((matching_req && matching_req == limit && break_dispatch) ||
310 loop_again = !requests.empty () || (event_count > 0);
316 * @brief convenience wrapper function
318 vapi_error_e dispatch (const Common_req &limit)
320 return dispatch (&limit);
324 * @brief wait for response to a specific request
326 * @param req request to wait for response for
328 * @return VAPI_OK on success, other error code on error
330 vapi_error_e wait_for_response (const Common_req &req)
332 if (RESPONSE_READY == req.get_response_state ())
336 return dispatch (req);
340 void msg_free (void *shm_data)
342 #if VAPI_CPP_DEBUG_LEAKS
343 on_shm_data_free (shm_data);
345 vapi_msg_free (vapi_ctx, shm_data);
348 template <template <typename XReq, typename XResp, typename... XArgs>
350 typename Req, typename Resp, typename... Args>
351 vapi_error_e send (X<Req, Resp, Args...> *req)
358 req_context_counter.fetch_add (1, std::memory_order_relaxed);
359 req->request.shm_data->header.context = req_context;
360 vapi_swap_to_be<Req> (req->request.shm_data);
361 std::lock_guard<std::recursive_mutex> lock (requests_mutex);
362 vapi_error_e rv = vapi_send (vapi_ctx, req->request.shm_data);
365 VAPI_DBG ("Push %p", req);
366 requests.emplace_back (req);
367 req->set_context (req_context);
368 #if VAPI_CPP_DEBUG_LEAKS
369 on_shm_data_free (req->request.shm_data);
371 req->request.shm_data = nullptr; /* consumed by vapi_send */
375 vapi_swap_to_host<Req> (req->request.shm_data);
380 template <template <typename XReq, typename XResp, typename... XArgs>
382 typename Req, typename Resp, typename... Args>
383 vapi_error_e send_with_control_ping (X<Req, Resp, Args...> *req)
390 req_context_counter.fetch_add (1, std::memory_order_relaxed);
391 req->request.shm_data->header.context = req_context;
392 vapi_swap_to_be<Req> (req->request.shm_data);
393 std::lock_guard<std::recursive_mutex> lock (requests_mutex);
394 vapi_error_e rv = vapi_send_with_control_ping (
395 vapi_ctx, req->request.shm_data, req_context);
398 VAPI_DBG ("Push %p", req);
399 requests.emplace_back (req);
400 req->set_context (req_context);
401 #if VAPI_CPP_DEBUG_LEAKS
402 on_shm_data_free (req->request.shm_data);
404 req->request.shm_data = nullptr; /* consumed by vapi_send */
408 vapi_swap_to_host<Req> (req->request.shm_data);
413 void unregister_request (Common_req *request)
415 std::lock_guard<std::recursive_mutex> lock (requests_mutex);
416 std::remove (requests.begin (), requests.end (), request);
419 template <typename M> void register_event (Event_registration<M> *event)
421 const vapi_msg_id_t id = M::get_msg_id ();
422 std::lock_guard<std::recursive_mutex> lock (events_mutex);
427 template <typename M> void unregister_event (Event_registration<M> *event)
429 const vapi_msg_id_t id = M::get_msg_id ();
430 std::lock_guard<std::recursive_mutex> lock (events_mutex);
431 events[id] = nullptr;
436 std::atomic_ulong req_context_counter;
437 std::mutex dispatch_mutex;
439 std::recursive_mutex requests_mutex;
440 std::recursive_mutex events_mutex;
441 std::deque<Common_req *> requests;
442 std::vector<Common_req *> events;
445 template <typename Req, typename Resp, typename... Args>
446 friend class Request;
448 template <typename Req, typename Resp, typename... Args> friend class Dump;
450 template <typename M> friend class Result_set;
452 template <typename M> friend class Event_registration;
454 template <typename M, typename... Args>
455 friend M *vapi_alloc (Connection &con, Args...);
457 template <typename M> friend class Msg;
459 #if VAPI_CPP_DEBUG_LEAKS
460 void on_shm_data_alloc (void *shm_data)
464 auto pos = shm_data_set.find (shm_data);
465 if (pos == shm_data_set.end ())
467 shm_data_set.insert (shm_data);
471 printf ("Double-add shm_data @%p!\n", shm_data);
476 void on_shm_data_free (void *shm_data)
478 auto pos = shm_data_set.find (shm_data);
479 if (pos == shm_data_set.end ())
481 printf ("Freeing untracked shm_data @%p!\n", shm_data);
485 shm_data_set.erase (pos);
488 std::unordered_set<void *> shm_data_set;
492 template <typename Req, typename Resp, typename... Args> class Request;
494 template <typename Req, typename Resp, typename... Args> class Dump;
496 template <class, class = void> struct vapi_has_payload_trait : std::false_type
500 template <class... T> using vapi_void_t = void;
503 struct vapi_has_payload_trait<T, vapi_void_t<decltype (&T::payload)>>
508 template <typename M> void vapi_msg_set_msg_id (vapi_msg_id_t id)
510 Msg<M>::set_msg_id (id);
514 * Class representing a message stored in shared memory
516 template <typename M> class Msg
519 Msg (const Msg &) = delete;
523 VAPI_DBG ("Destroy Msg<%s>@%p, shm_data@%p",
524 vapi_get_msg_name (get_msg_id ()), this, shm_data);
527 con.get ().msg_free (shm_data);
532 static vapi_msg_id_t get_msg_id ()
534 return *msg_id_holder ();
537 template <typename X = M>
538 typename std::enable_if<vapi_has_payload_trait<X>::value,
539 decltype (X::payload) &>::type
542 return shm_data->payload;
546 Msg (Msg<M> &&msg) : con{msg.con}
548 VAPI_DBG ("Move construct Msg<%s> from msg@%p to msg@%p, shm_data@%p",
549 vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data);
550 shm_data = msg.shm_data;
551 msg.shm_data = nullptr;
554 Msg<M> &operator= (Msg<M> &&msg)
556 VAPI_DBG ("Move assign Msg<%s> from msg@%p to msg@%p, shm_data@%p",
557 vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data);
558 con.get ().msg_free (shm_data);
560 shm_data = msg.shm_data;
561 msg.shm_data = nullptr;
565 struct Msg_allocator : std::allocator<Msg<M>>
567 template <class U, class... Args> void construct (U *p, Args &&... args)
569 ::new ((void *)p) U (std::forward<Args> (args)...);
572 template <class U> struct rebind
574 typedef Msg_allocator other;
578 static void set_msg_id (vapi_msg_id_t id)
580 assert ((~0 == *msg_id_holder ()) || (id == *msg_id_holder ()));
581 *msg_id_holder () = id;
584 static vapi_msg_id_t *msg_id_holder ()
586 static vapi_msg_id_t my_id{~0};
590 Msg (Connection &con, void *shm_data) : con{con}
592 if (!con.is_msg_available (get_msg_id ()))
594 throw Msg_not_available_exception ();
596 this->shm_data = static_cast<shm_data_type *> (shm_data);
597 VAPI_DBG ("New Msg<%s>@%p shm_data@%p", vapi_get_msg_name (get_msg_id ()),
601 void assign_response (vapi_msg_id_t resp_id, void *shm_data)
603 assert (nullptr == this->shm_data);
604 if (resp_id != get_msg_id ())
606 throw Unexpected_msg_id_exception ();
608 this->shm_data = static_cast<M *> (shm_data);
609 vapi_swap_to_host<M> (this->shm_data);
610 VAPI_DBG ("Assign response to Msg<%s>@%p shm_data@%p",
611 vapi_get_msg_name (get_msg_id ()), this, shm_data);
614 std::reference_wrapper<Connection> con;
615 using shm_data_type = M;
616 shm_data_type *shm_data;
618 friend class Connection;
620 template <typename Req, typename Resp, typename... Args>
621 friend class Request;
623 template <typename Req, typename Resp, typename... Args> friend class Dump;
625 template <typename X> friend class Event_registration;
627 template <typename X> friend class Result_set;
629 friend struct Msg_allocator;
631 template <typename X> friend void vapi_msg_set_msg_id (vapi_msg_id_t id);
635 * Class representing a simple request - with a single response message
637 template <typename Req, typename Resp, typename... Args>
638 class Request : public Common_req
641 Request (Connection &con, Args... args,
642 std::function<vapi_error_e (Request<Req, Resp, Args...> &)>
644 : Common_req{con}, callback{callback},
645 request{con, vapi_alloc<Req> (con, args...)}, response{con, nullptr}
649 Request (const Request &) = delete;
653 if (RESPONSE_NOT_READY == get_response_state ())
655 con.unregister_request (this);
659 vapi_error_e execute ()
661 return con.send (this);
664 const Msg<Req> &get_request (void) const
669 const Msg<Resp> &get_response (void)
675 virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
678 assert (RESPONSE_NOT_READY == get_response_state ());
679 response.assign_response (id, shm_data);
680 set_response_state (RESPONSE_READY);
681 if (nullptr != callback)
683 return std::make_pair (callback (*this), true);
685 return std::make_pair (VAPI_OK, true);
687 std::function<vapi_error_e (Request<Req, Resp, Args...> &)> callback;
691 friend class Connection;
695 * Class representing iterable set of responses of the same type
697 template <typename M> class Result_set
704 Result_set (const Result_set &) = delete;
706 bool is_complete () const
716 using const_iterator =
717 typename std::vector<Msg<M>,
718 typename Msg<M>::Msg_allocator>::const_iterator;
720 const_iterator begin () const
725 const_iterator end () const
730 void free_response (const_iterator pos)
735 void free_all_responses ()
741 void mark_complete ()
746 void assign_response (vapi_msg_id_t resp_id, void *shm_data)
748 if (resp_id != Msg<M>::get_msg_id ())
751 throw Unexpected_msg_id_exception ();
756 vapi_swap_to_host<M> (static_cast<M *> (shm_data));
757 set.emplace_back (con, shm_data);
758 VAPI_DBG ("Result_set@%p emplace_back shm_data@%p", this, shm_data);
762 Result_set (Connection &con) : con (con), complete{false}
768 std::vector<Msg<M>, typename Msg<M>::Msg_allocator> set;
770 template <typename Req, typename Resp, typename... Args> friend class Dump;
772 template <typename X> friend class Event_registration;
776 * Class representing a dump request - zero or more identical responses to a
777 * single request message
779 template <typename Req, typename Resp, typename... Args>
780 class Dump : public Common_req
783 Dump (Connection &con, Args... args,
784 std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback =
786 : Common_req{con}, request{con, vapi_alloc<Req> (con, args...)},
787 result_set{con}, callback{callback}
791 Dump (const Dump &) = delete;
797 virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
800 if (id == vapi_msg_id_control_ping_reply)
802 con.msg_free (shm_data);
803 result_set.mark_complete ();
804 set_response_state (RESPONSE_READY);
805 if (nullptr != callback)
807 return std::make_pair (callback (*this), true);
809 return std::make_pair (VAPI_OK, true);
813 result_set.assign_response (id, shm_data);
815 return std::make_pair (VAPI_OK, false);
818 vapi_error_e execute ()
820 return con.send_with_control_ping (this);
823 Msg<Req> &get_request (void)
828 using resp_type = typename Msg<Resp>::shm_data_type;
830 const Result_set<Resp> &get_result_set (void) const
837 Result_set<resp_type> result_set;
838 std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback;
840 friend class Connection;
844 * Class representing event registration - incoming events (messages) from
845 * vpp as a result of a subscription (typically a want_* simple request)
847 template <typename M> class Event_registration : public Common_req
852 std::function<vapi_error_e (Event_registration<M> &)> callback = nullptr)
853 : Common_req{con}, result_set{con}, callback{callback}
855 if (!con.is_msg_available (M::get_msg_id ()))
857 throw Msg_not_available_exception ();
859 con.register_event (this);
862 Event_registration (const Event_registration &) = delete;
864 virtual ~Event_registration ()
866 con.unregister_event (this);
869 virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
872 result_set.assign_response (id, shm_data);
873 if (nullptr != callback)
875 return std::make_pair (callback (*this), true);
877 return std::make_pair (VAPI_OK, true);
880 using resp_type = typename M::shm_data_type;
882 Result_set<resp_type> &get_result_set (void)
888 Result_set<resp_type> result_set;
889 std::function<vapi_error_e (Event_registration<M> &)> callback;
896 * fd.io coding-style-patch-verification: ON
899 * eval: (c-set-style "gnu")