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)
109 : con (con), context{0}, response_state{RESPONSE_NOT_READY}
113 void set_response_state (vapi_response_state_e state)
115 response_state = state;
118 virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
121 void set_context (u32 context)
123 this->context = context;
132 vapi_response_state_e response_state;
134 friend class Connection;
136 template <typename M> friend class Msg;
138 template <typename Req, typename Resp, typename... Args>
139 friend class Request;
141 template <typename Req, typename Resp, typename... Args> friend class Dump;
143 template <typename Req, typename Resp, typename StreamMessage,
147 template <typename M> friend class Event_registration;
151 * Class representing a connection to VPP
153 * After creating a Connection object, call connect() to actually connect
154 * to VPP. Use is_msg_available to discover whether a specific message is known
155 * and supported by the VPP connected to.
160 Connection (void) : vapi_ctx{0}, event_count{0}
163 vapi_error_e rv = VAPI_OK;
166 if (VAPI_OK != (rv = vapi_ctx_alloc (&vapi_ctx)))
168 throw std::bad_alloc ();
171 events.reserve (vapi_get_message_count () + 1);
174 Connection (const Connection &) = delete;
178 vapi_ctx_free (vapi_ctx);
179 #if VAPI_CPP_DEBUG_LEAKS
180 for (auto x : shm_data_set)
182 printf ("Leaked shm_data@%p!\n", x);
188 * @brief check if message identified by it's message id is known by the
189 * vpp to which the connection is open
191 bool is_msg_available (vapi_msg_id_t type)
193 return vapi_is_msg_available (vapi_ctx, type);
197 * @brief connect to vpp
199 * @param name application name
200 * @param chroot_prefix shared memory prefix
201 * @param max_queued_request max number of outstanding requests queued
202 * @param handle_keepalives handle memclnt_keepalive automatically
204 * @return VAPI_OK on success, other error code on error
207 connect (const char *name, const char *chroot_prefix,
208 int max_outstanding_requests, int response_queue_size,
209 bool handle_keepalives = true, bool use_uds = false)
211 return vapi_connect_ex (vapi_ctx, name, chroot_prefix,
212 max_outstanding_requests, response_queue_size,
213 VAPI_MODE_BLOCKING, handle_keepalives, use_uds);
217 * @brief disconnect from vpp
219 * @return VAPI_OK on success, other error code on error
221 vapi_error_e disconnect ()
223 auto x = requests.size ();
226 VAPI_DBG ("popping request @%p", requests.front ());
227 requests.pop_front ();
230 return vapi_disconnect (vapi_ctx);
234 * @brief get event file descriptor
236 * @note this file descriptor becomes readable when messages (from vpp)
237 * are waiting in queue
239 * @param[out] fd pointer to result variable
241 * @return VAPI_OK on success, other error code on error
243 vapi_error_e get_fd (int *fd)
245 return vapi_get_fd (vapi_ctx, fd);
249 * @brief wait for responses from vpp and assign them to appropriate objects
251 * @param limit stop dispatch after the limit object received it's response
253 * @return VAPI_OK on success, other error code on error
255 vapi_error_e dispatch (const Common_req *limit = nullptr, u32 time = 5)
257 std::lock_guard<std::mutex> lock (dispatch_mutex);
258 vapi_error_e rv = VAPI_OK;
259 bool loop_again = true;
263 size_t shm_data_size;
264 rv = vapi_recv (vapi_ctx, &shm_data, &shm_data_size, SVM_Q_TIMEDWAIT,
270 #if VAPI_CPP_DEBUG_LEAKS
271 on_shm_data_alloc (shm_data);
273 std::lock_guard<std::recursive_mutex> requests_lock (requests_mutex);
274 std::lock_guard<std::recursive_mutex> events_lock (events_mutex);
275 vapi_msg_id_t id = vapi_lookup_vapi_msg_id_t (
276 vapi_ctx, be16toh (*static_cast<u16 *> (shm_data)));
277 bool has_context = vapi_msg_is_with_context (id);
278 bool break_dispatch = false;
279 Common_req *matching_req = nullptr;
282 u32 context = *reinterpret_cast<u32 *> (
283 (static_cast<u8 *> (shm_data) + vapi_get_context_offset (id)));
284 const auto x = requests.front ();
286 if (context == x->context)
288 std::tie (rv, break_dispatch) =
289 x->assign_response (id, shm_data);
293 std::tie (rv, break_dispatch) =
294 x->assign_response (id, nullptr);
298 requests.pop_front ();
305 std::tie (rv, break_dispatch) =
306 events[id]->assign_response (id, shm_data);
307 matching_req = events[id];
314 if ((matching_req && matching_req == limit && break_dispatch) ||
319 loop_again = !requests.empty () || (event_count > 0);
325 * @brief convenience wrapper function
327 vapi_error_e dispatch (const Common_req &limit)
329 return dispatch (&limit);
333 * @brief wait for response to a specific request
335 * @param req request to wait for response for
337 * @return VAPI_OK on success, other error code on error
339 vapi_error_e wait_for_response (const Common_req &req)
341 if (RESPONSE_READY == req.get_response_state ())
345 return dispatch (req);
349 void msg_free (void *shm_data)
351 #if VAPI_CPP_DEBUG_LEAKS
352 on_shm_data_free (shm_data);
354 vapi_msg_free (vapi_ctx, shm_data);
357 template <template <typename XReq, typename XResp, typename... XArgs>
359 typename Req, typename Resp, typename... Args>
360 vapi_error_e send (X<Req, Resp, Args...> *req)
367 req_context_counter.fetch_add (1, std::memory_order_relaxed);
368 req->request.shm_data->header.context = req_context;
369 vapi_swap_to_be<Req> (req->request.shm_data);
370 std::lock_guard<std::recursive_mutex> lock (requests_mutex);
371 vapi_error_e rv = vapi_send (vapi_ctx, req->request.shm_data);
374 VAPI_DBG ("Push %p", req);
375 requests.emplace_back (req);
376 req->set_context (req_context);
377 #if VAPI_CPP_DEBUG_LEAKS
378 on_shm_data_free (req->request.shm_data);
380 req->request.shm_data = nullptr; /* consumed by vapi_send */
384 vapi_swap_to_host<Req> (req->request.shm_data);
389 template <template <typename XReq, typename XResp, typename... XArgs>
391 typename Req, typename Resp, typename... Args>
392 vapi_error_e send_with_control_ping (X<Req, Resp, Args...> *req)
399 req_context_counter.fetch_add (1, std::memory_order_relaxed);
400 req->request.shm_data->header.context = req_context;
401 vapi_swap_to_be<Req> (req->request.shm_data);
402 std::lock_guard<std::recursive_mutex> lock (requests_mutex);
403 vapi_error_e rv = vapi_send_with_control_ping (
404 vapi_ctx, req->request.shm_data, req_context);
407 VAPI_DBG ("Push %p", req);
408 requests.emplace_back (req);
409 req->set_context (req_context);
410 #if VAPI_CPP_DEBUG_LEAKS
411 on_shm_data_free (req->request.shm_data);
413 req->request.shm_data = nullptr; /* consumed by vapi_send */
417 vapi_swap_to_host<Req> (req->request.shm_data);
422 void unregister_request (Common_req *request)
424 std::lock_guard<std::recursive_mutex> lock (requests_mutex);
425 std::remove (requests.begin (), requests.end (), request);
428 template <typename M> void register_event (Event_registration<M> *event)
430 const vapi_msg_id_t id = M::get_msg_id ();
431 std::lock_guard<std::recursive_mutex> lock (events_mutex);
436 template <typename M> void unregister_event (Event_registration<M> *event)
438 const vapi_msg_id_t id = M::get_msg_id ();
439 std::lock_guard<std::recursive_mutex> lock (events_mutex);
440 events[id] = nullptr;
445 std::atomic_ulong req_context_counter;
446 std::mutex dispatch_mutex;
448 std::recursive_mutex requests_mutex;
449 std::recursive_mutex events_mutex;
450 std::deque<Common_req *> requests;
451 std::vector<Common_req *> events;
454 template <typename Req, typename Resp, typename... Args>
455 friend class Request;
457 template <typename Req, typename Resp, typename... Args> friend class Dump;
459 template <typename Req, typename Resp, typename StreamMessage,
463 template <typename M> friend class Result_set;
465 template <typename M> friend class Event_registration;
467 template <typename M, typename... Args>
468 friend M *vapi_alloc (Connection &con, Args...);
470 template <typename M> friend class Msg;
472 #if VAPI_CPP_DEBUG_LEAKS
473 void on_shm_data_alloc (void *shm_data)
477 auto pos = shm_data_set.find (shm_data);
478 if (pos == shm_data_set.end ())
480 shm_data_set.insert (shm_data);
484 printf ("Double-add shm_data @%p!\n", shm_data);
489 void on_shm_data_free (void *shm_data)
491 auto pos = shm_data_set.find (shm_data);
492 if (pos == shm_data_set.end ())
494 printf ("Freeing untracked shm_data @%p!\n", shm_data);
498 shm_data_set.erase (pos);
501 std::unordered_set<void *> shm_data_set;
505 template <typename Req, typename Resp, typename... Args> class Request;
507 template <typename Req, typename Resp, typename... Args> class Dump;
509 template <typename Req, typename Resp, typename StreamMessage,
513 template <class, class = void> struct vapi_has_payload_trait : std::false_type
517 template <class... T> using vapi_void_t = void;
520 struct vapi_has_payload_trait<T, vapi_void_t<decltype (&T::payload)>>
525 template <typename M> void vapi_msg_set_msg_id (vapi_msg_id_t id)
527 Msg<M>::set_msg_id (id);
531 * Class representing a message stored in shared memory
533 template <typename M> class Msg
536 Msg (const Msg &) = delete;
540 VAPI_DBG ("Destroy Msg<%s>@%p, shm_data@%p",
541 vapi_get_msg_name (get_msg_id ()), this, shm_data);
544 con.get ().msg_free (shm_data);
549 static vapi_msg_id_t get_msg_id ()
551 return *msg_id_holder ();
554 template <typename X = M>
555 typename std::enable_if<vapi_has_payload_trait<X>::value,
556 decltype (X::payload) &>::type
559 return shm_data->payload;
563 Msg (Msg<M> &&msg) : con{msg.con}
565 VAPI_DBG ("Move construct Msg<%s> from msg@%p to msg@%p, shm_data@%p",
566 vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data);
567 shm_data = msg.shm_data;
568 msg.shm_data = nullptr;
571 Msg<M> &operator= (Msg<M> &&msg)
573 VAPI_DBG ("Move assign Msg<%s> from msg@%p to msg@%p, shm_data@%p",
574 vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data);
575 con.get ().msg_free (shm_data);
577 shm_data = msg.shm_data;
578 msg.shm_data = nullptr;
582 struct Msg_allocator : std::allocator<Msg<M>>
584 template <class U, class... Args> void construct (U *p, Args &&... args)
586 ::new ((void *)p) U (std::forward<Args> (args)...);
589 template <class U> struct rebind
591 typedef Msg_allocator other;
595 static void set_msg_id (vapi_msg_id_t id)
597 assert ((VAPI_INVALID_MSG_ID == *msg_id_holder ()) ||
598 (id == *msg_id_holder ()));
599 *msg_id_holder () = id;
602 static vapi_msg_id_t *msg_id_holder ()
604 static vapi_msg_id_t my_id{VAPI_INVALID_MSG_ID};
608 Msg (Connection &con, void *shm_data) : con{con}
610 if (!con.is_msg_available (get_msg_id ()))
612 throw Msg_not_available_exception ();
614 this->shm_data = static_cast<shm_data_type *> (shm_data);
615 VAPI_DBG ("New Msg<%s>@%p shm_data@%p", vapi_get_msg_name (get_msg_id ()),
619 void assign_response (vapi_msg_id_t resp_id, void *shm_data)
621 assert (nullptr == this->shm_data);
622 if (resp_id != get_msg_id ())
624 throw Unexpected_msg_id_exception ();
626 this->shm_data = static_cast<M *> (shm_data);
627 vapi_swap_to_host<M> (this->shm_data);
628 VAPI_DBG ("Assign response to Msg<%s>@%p shm_data@%p",
629 vapi_get_msg_name (get_msg_id ()), this, shm_data);
632 std::reference_wrapper<Connection> con;
633 using shm_data_type = M;
634 shm_data_type *shm_data;
636 friend class Connection;
638 template <typename Req, typename Resp, typename... Args>
639 friend class Request;
641 template <typename Req, typename Resp, typename... Args> friend class Dump;
643 template <typename Req, typename Resp, typename StreamMessage,
647 template <typename X> friend class Event_registration;
649 template <typename X> friend class Result_set;
651 friend struct Msg_allocator;
653 template <typename X> friend void vapi_msg_set_msg_id (vapi_msg_id_t id);
657 * Class representing a simple request - with a single response message
659 template <typename Req, typename Resp, typename... Args>
660 class Request : public Common_req
663 Request (Connection &con, Args... args,
664 std::function<vapi_error_e (Request<Req, Resp, Args...> &)>
666 : Common_req{ con }, callback{ std::move (callback) },
667 request{ con, vapi_alloc<Req> (con, args...) }, response{ con,
672 Request (const Request &) = delete;
676 if (RESPONSE_NOT_READY == get_response_state ())
678 con.unregister_request (this);
682 vapi_error_e execute ()
684 return con.send (this);
687 const Msg<Req> &get_request (void) const
692 const Msg<Resp> &get_response (void)
698 virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
701 assert (RESPONSE_NOT_READY == get_response_state ());
702 response.assign_response (id, shm_data);
703 set_response_state (RESPONSE_READY);
704 if (nullptr != callback)
706 return std::make_pair (callback (*this), true);
708 return std::make_pair (VAPI_OK, true);
710 std::function<vapi_error_e (Request<Req, Resp, Args...> &)> callback;
714 friend class Connection;
718 * Class representing iterable set of responses of the same type
720 template <typename M> class Result_set
727 Result_set (const Result_set &) = delete;
729 bool is_complete () const
739 using const_iterator =
740 typename std::vector<Msg<M>,
741 typename Msg<M>::Msg_allocator>::const_iterator;
743 const_iterator begin () const
748 const_iterator end () const
753 void free_response (const_iterator pos)
758 void free_all_responses ()
764 void mark_complete ()
769 void assign_response (vapi_msg_id_t resp_id, void *shm_data)
771 if (resp_id != Msg<M>::get_msg_id ())
774 throw Unexpected_msg_id_exception ();
779 vapi_swap_to_host<M> (static_cast<M *> (shm_data));
780 set.emplace_back (con, shm_data);
781 VAPI_DBG ("Result_set@%p emplace_back shm_data@%p", this, shm_data);
785 Result_set (Connection &con) : con (con), complete{false}
791 std::vector<Msg<M>, typename Msg<M>::Msg_allocator> set;
793 template <typename Req, typename Resp, typename StreamMessage,
797 template <typename Req, typename Resp, typename... Args> friend class Dump;
799 template <typename X> friend class Event_registration;
803 * Class representing a RPC request - zero or more identical responses to a
804 * single request message with a response
806 template <typename Req, typename Resp, typename StreamMessage,
808 class Stream : public Common_req
812 Connection &con, Args... args,
813 std::function<vapi_error_e (Stream<Req, Resp, StreamMessage, Args...> &)>
815 : Common_req{ con }, request{ con, vapi_alloc<Req> (con, args...) },
816 response{ con, nullptr }, result_set{ con }, callback{ std::move (cb) }
820 Stream (const Stream &) = delete;
822 virtual ~Stream () {}
824 virtual std::tuple<vapi_error_e, bool>
825 assign_response (vapi_msg_id_t id, void *shm_data)
827 if (id == response.get_msg_id ())
829 response.assign_response (id, shm_data);
830 result_set.mark_complete ();
831 set_response_state (RESPONSE_READY);
832 if (nullptr != callback)
834 return std::make_pair (callback (*this), true);
836 return std::make_pair (VAPI_OK, true);
840 result_set.assign_response (id, shm_data);
842 return std::make_pair (VAPI_OK, false);
848 return con.send (this);
863 using resp_type = typename Msg<StreamMessage>::shm_data_type;
865 const Result_set<StreamMessage> &
866 get_result_set (void) const
874 Result_set<StreamMessage> result_set;
875 std::function<vapi_error_e (Stream<Req, Resp, StreamMessage, Args...> &)>
878 friend class Connection;
879 friend class Result_set<StreamMessage>;
883 * Class representing a dump request - zero or more identical responses to a
884 * single request message
886 template <typename Req, typename Resp, typename... Args>
887 class Dump : public Common_req
890 Dump (Connection &con, Args... args,
891 std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback =
893 : Common_req{ con }, request{ con, vapi_alloc<Req> (con, args...) },
894 result_set{ con }, callback{ std::move (callback) }
898 Dump (const Dump &) = delete;
904 virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
907 if (id == vapi_msg_id_control_ping_reply)
909 con.msg_free (shm_data);
910 result_set.mark_complete ();
911 set_response_state (RESPONSE_READY);
912 if (nullptr != callback)
914 return std::make_pair (callback (*this), true);
916 return std::make_pair (VAPI_OK, true);
920 result_set.assign_response (id, shm_data);
922 return std::make_pair (VAPI_OK, false);
925 vapi_error_e execute ()
927 return con.send_with_control_ping (this);
930 Msg<Req> &get_request (void)
935 using resp_type = typename Msg<Resp>::shm_data_type;
937 const Result_set<Resp> &get_result_set (void) const
944 Result_set<resp_type> result_set;
945 std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback;
947 friend class Connection;
951 * Class representing event registration - incoming events (messages) from
952 * vpp as a result of a subscription (typically a want_* simple request)
954 template <typename M> class Event_registration : public Common_req
959 std::function<vapi_error_e (Event_registration<M> &)> callback = nullptr)
960 : Common_req{ con }, result_set{ con }, callback{ std::move (callback) }
962 if (!con.is_msg_available (M::get_msg_id ()))
964 throw Msg_not_available_exception ();
966 con.register_event (this);
969 Event_registration (const Event_registration &) = delete;
971 virtual ~Event_registration ()
973 con.unregister_event (this);
976 virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
979 result_set.assign_response (id, shm_data);
980 if (nullptr != callback)
982 return std::make_pair (callback (*this), true);
984 return std::make_pair (VAPI_OK, true);
987 using resp_type = typename M::shm_data_type;
989 Result_set<resp_type> &get_result_set (void)
995 Result_set<resp_type> result_set;
996 std::function<vapi_error_e (Event_registration<M> &)> callback;
1003 * fd.io coding-style-patch-verification: ON
1006 * eval: (c-set-style "gnu")