/* *------------------------------------------------------------------ * Copyright (c) 2017 Cisco and/or its affiliates. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *------------------------------------------------------------------ */ #ifndef vapi_hpp_included #define vapi_hpp_included #include <cstddef> #include <vector> #include <mutex> #include <queue> #include <cassert> #include <functional> #include <algorithm> #include <atomic> #include <vppinfra/types.h> #include <vapi/vapi.h> #include <vapi/vapi_internal.h> #include <vapi/vapi_dbg.h> #include <vapi/vpe.api.vapi.h> #if VAPI_CPP_DEBUG_LEAKS #include <unordered_set> #endif /** * @file * @brief C++ VPP API */ namespace vapi { class Connection; template <typename Req, typename Resp, typename... Args> class Request; template <typename M> class Msg; template <typename M> void vapi_swap_to_be (M *msg); template <typename M> void vapi_swap_to_host (M *msg); template <typename M, typename... Args> M *vapi_alloc (Connection &con, Args...); template <typename M> vapi_msg_id_t vapi_get_msg_id_t (); template <typename M> class Event_registration; class Unexpected_msg_id_exception : public std::exception { public: virtual const char *what () const throw () { return "unexpected message id"; } }; class Msg_not_available_exception : public std::exception { public: virtual const char *what () const throw () { return "message unavailable"; } }; typedef enum { /** response not ready yet */ RESPONSE_NOT_READY, /** response to request is ready */ RESPONSE_READY, /** no response to request (will never come) */ RESPONSE_NO_RESPONSE, } vapi_response_state_e; /** * Class representing common functionality of a request - response state * and context */ class Common_req { public: virtual ~Common_req (){}; Connection &get_connection () { return con; }; vapi_response_state_e get_response_state (void) const { return response_state; } private: Connection &con; Common_req (Connection &con) : con (con), context{0}, response_state{RESPONSE_NOT_READY} { } void set_response_state (vapi_response_state_e state) { response_state = state; } virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id, void *shm_data) = 0; void set_context (u32 context) { this->context = context; } u32 get_context () { return context; } u32 context; vapi_response_state_e response_state; friend class Connection; template <typename M> friend class Msg; template <typename Req, typename Resp, typename... Args> friend class Request; template <typename Req, typename Resp, typename... Args> friend class Dump; template <typename M> friend class Event_registration; }; /** * Class representing a connection to VPP * * After creating a Connection object, call connect() to actually connect * to VPP. Use is_msg_available to discover whether a specific message is known * and supported by the VPP connected to. */ class Connection { public: Connection (void) : vapi_ctx{0}, event_count{0} { vapi_error_e rv = VAPI_OK; if (!vapi_ctx) { if (VAPI_OK != (rv = vapi_ctx_alloc (&vapi_ctx))) { throw std::bad_alloc (); } } events.reserve (vapi_get_message_count () + 1); } Connection (const Connection &) = delete; ~Connection (void) { vapi_ctx_free (vapi_ctx); #if VAPI_CPP_DEBUG_LEAKS for (auto x : shm_data_set) { printf ("Leaked shm_data@%p!\n", x); } #endif } /** * @brief check if message identified by it's message id is known by the * vpp to which the connection is open */ bool is_msg_available (vapi_msg_id_t type) { return vapi_is_msg_available (vapi_ctx, type); } /** * @brief connect to vpp * * @param name application name * @param chroot_prefix shared memory prefix * @param max_queued_request max number of outstanding requests queued * @param handle_keepalives handle memclnt_keepalive automatically * * @return VAPI_OK on success, other error code on error */ vapi_error_e connect (const char *name, const char *chroot_prefix, int max_outstanding_requests, int response_queue_size, bool handle_keepalives = true) { return vapi_connect (vapi_ctx, name, chroot_prefix, max_outstanding_requests, response_queue_size, VAPI_MODE_BLOCKING, handle_keepalives); } /** * @brief disconnect from vpp * * @return VAPI_OK on success, other error code on error */ vapi_error_e disconnect () { auto x = requests.size (); while (x > 0) { VAPI_DBG ("popping request @%p", requests.front ()); requests.pop_front (); --x; } return vapi_disconnect (vapi_ctx); }; /** * @brief get event file descriptor * * @note this file descriptor becomes readable when messages (from vpp) * are waiting in queue * * @param[out] fd pointer to result variable * * @return VAPI_OK on success, other error code on error */ vapi_error_e get_fd (int *fd) { return vapi_get_fd (vapi_ctx, fd); } /** * @brief wait for responses from vpp and assign them to appropriate objects * * @param limit stop dispatch after the limit object received it's response * * @return VAPI_OK on success, other error code on error */ vapi_error_e dispatch (const Common_req *limit = nullptr, u32 time = 5) { std::lock_guard<std::mutex> lock (dispatch_mutex); vapi_error_e rv = VAPI_OK; bool loop_again = true; while (loop_again) { void *shm_data; size_t shm_data_size; rv = vapi_recv (vapi_ctx, &shm_data, &shm_data_size, SVM_Q_TIMEDWAIT, time); if (VAPI_OK != rv) { return rv; } #if VAPI_CPP_DEBUG_LEAKS on_shm_data_alloc (shm_data); #endif std::lock_guard<std::recursive_mutex> requests_lock (requests_mutex); std::lock_guard<std::recursive_mutex> events_lock (events_mutex); vapi_msg_id_t id = vapi_lookup_vapi_msg_id_t ( vapi_ctx, be16toh (*static_cast<u16 *> (shm_data))); bool has_context = vapi_msg_is_with_context (id); bool break_dispatch = false; Common_req *matching_req = nullptr; if (has_context) { u32 context = *reinterpret_cast<u32 *> ( (static_cast<u8 *> (shm_data) + vapi_get_context_offset (id))); const auto x = requests.front (); matching_req = x; if (context == x->context) { std::tie (rv, break_dispatch) = x->assign_response (id, shm_data); } else { std::tie (rv, break_dispatch) = x->assign_response (id, nullptr); } if (break_dispatch) { requests.pop_front (); } } else { if (events[id]) { std::tie (rv, break_dispatch) = events[id]->assign_response (id, shm_data); matching_req = events[id]; } else { msg_free (shm_data); } } if ((matching_req && matching_req == limit && break_dispatch) || VAPI_OK != rv) { return rv; } loop_again = !requests.empty () || (event_count > 0); } return rv; } /** * @brief convenience wrapper function */ vapi_error_e dispatch (const Common_req &limit) { return dispatch (&limit); } /** * @brief wait for response to a specific request * * @param req request to wait for response for * * @return VAPI_OK on success, other error code on error */ vapi_error_e wait_for_response (const Common_req &req) { if (RESPONSE_READY == req.get_response_state ()) { return VAPI_OK; } return dispatch (req); } private: void msg_free (void *shm_data) { #if VAPI_CPP_DEBUG_LEAKS on_shm_data_free (shm_data); #endif vapi_msg_free (vapi_ctx, shm_data); } template <template <typename XReq, typename XResp, typename... XArgs> class X, typename Req, typename Resp, typename... Args> vapi_error_e send (X<Req, Resp, Args...> *req) { if (!req) { return VAPI_EINVAL; } u32 req_context = req_context_counter.fetch_add (1, std::memory_order_relaxed); req->request.shm_data->header.context = req_context; vapi_swap_to_be<Req> (req->request.shm_data); std::lock_guard<std::recursive_mutex> lock (requests_mutex); vapi_error_e rv = vapi_send (vapi_ctx, req->request.shm_data); if (VAPI_OK == rv) { VAPI_DBG ("Push %p", req); requests.emplace_back (req); req->set_context (req_context); #if VAPI_CPP_DEBUG_LEAKS on_shm_data_free (req->request.shm_data); #endif req->request.shm_data = nullptr; /* consumed by vapi_send */ } else { vapi_swap_to_host<Req> (req->request.shm_data); } return rv; } template <template <typename XReq, typename XResp, typename... XArgs> class X, typename Req, typename Resp, typename... Args> vapi_error_e send_with_control_ping (X<Req, Resp, Args...> *req) { if (!req) { return VAPI_EINVAL; } u32 req_context = req_context_counter.fetch_add (1, std::memory_order_relaxed); req->request.shm_data->header.context = req_context; vapi_swap_to_be<Req> (req->request.shm_data); std::lock_guard<std::recursive_mutex> lock (requests_mutex); vapi_error_e rv = vapi_send_with_control_ping ( vapi_ctx, req->request.shm_data, req_context); if (VAPI_OK == rv) { VAPI_DBG ("Push %p", req); requests.emplace_back (req); req->set_context (req_context); #if VAPI_CPP_DEBUG_LEAKS on_shm_data_free (req->request.shm_data); #endif req->request.shm_data = nullptr; /* consumed by vapi_send */ } else { vapi_swap_to_host<Req> (req->request.shm_data); } return rv; } void unregister_request (Common_req *request) { std::lock_guard<std::recursive_mutex> lock (requests_mutex); std::remove (requests.begin (), requests.end (), request); } template <typename M> void register_event (Event_registration<M> *event) { const vapi_msg_id_t id = M::get_msg_id (); std::lock_guard<std::recursive_mutex> lock (events_mutex); events[id] = event; ++event_count; } template <typename M> void unregister_event (Event_registration<M> *event) { const vapi_msg_id_t id = M::get_msg_id (); std::lock_guard<std::recursive_mutex> lock (events_mutex); events[id] = nullptr; --event_count; } vapi_ctx_t vapi_ctx; std::atomic_ulong req_context_counter; std::mutex dispatch_mutex; std::recursive_mutex requests_mutex; std::recursive_mutex events_mutex; std::deque<Common_req *> requests; std::vector<Common_req *> events; int event_count; template <typename Req, typename Resp, typename... Args> friend class Request; template <typename Req, typename Resp, typename... Args> friend class Dump; template <typename M> friend class Result_set; template <typename M> friend class Event_registration; template <typename M, typename... Args> friend M *vapi_alloc (Connection &con, Args...); template <typename M> friend class Msg; #if VAPI_CPP_DEBUG_LEAKS void on_shm_data_alloc (void *shm_data) { if (shm_data) { auto pos = shm_data_set.find (shm_data); if (pos == shm_data_set.end ()) { shm_data_set.insert (shm_data); } else { printf ("Double-add shm_data @%p!\n", shm_data); } } } void on_shm_data_free (void *shm_data) { auto pos = shm_data_set.find (shm_data); if (pos == shm_data_set.end ()) { printf ("Freeing untracked shm_data @%p!\n", shm_data); } else { shm_data_set.erase (pos); } } std::unordered_set<void *> shm_data_set; #endif }; template <typename Req, typename Resp, typename... Args> class Request; template <typename Req, typename Resp, typename... Args> class Dump; template <class, class = void> struct vapi_has_payload_trait : std::false_type { }; template <class... T> using vapi_void_t = void; template <class T> struct vapi_has_payload_trait<T, vapi_void_t<decltype (&T::payload)>> : std::true_type { }; template <typename M> void vapi_msg_set_msg_id (vapi_msg_id_t id) { Msg<M>::set_msg_id (id); } /** * Class representing a message stored in shared memory */ template <typename M> class Msg { public: Msg (const Msg &) = delete; ~Msg () { VAPI_DBG ("Destroy Msg<%s>@%p, shm_data@%p", vapi_get_msg_name (get_msg_id ()), this, shm_data); if (shm_data) { con.get ().msg_free (shm_data); shm_data = nullptr; } } static vapi_msg_id_t get_msg_id () { return *msg_id_holder (); } template <typename X = M> typename std::enable_if<vapi_has_payload_trait<X>::value, decltype (X::payload) &>::type get_payload () const { return shm_data->payload; } private: Msg (Msg<M> &&msg) : con{msg.con} { VAPI_DBG ("Move construct Msg<%s> from msg@%p to msg@%p, shm_data@%p", vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data); shm_data = msg.shm_data; msg.shm_data = nullptr; } Msg<M> &operator= (Msg<M> &&msg) { VAPI_DBG ("Move assign Msg<%s> from msg@%p to msg@%p, shm_data@%p", vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data); con.get ().msg_free (shm_data); con = msg.con; shm_data = msg.shm_data; msg.shm_data = nullptr; return *this; } struct Msg_allocator : std::allocator<Msg<M>> { template <class U, class... Args> void construct (U *p, Args &&... args) { ::new ((void *)p) U (std::forward<Args> (args)...); } template <class U> struct rebind { typedef Msg_allocator other; }; }; static void set_msg_id (vapi_msg_id_t id) { assert ((VAPI_INVALID_MSG_ID == *msg_id_holder ()) || (id == *msg_id_holder ())); *msg_id_holder () = id; } static vapi_msg_id_t *msg_id_holder () { static vapi_msg_id_t my_id{VAPI_INVALID_MSG_ID}; return &my_id; } Msg (Connection &con, void *shm_data) : con{con} { if (!con.is_msg_available (get_msg_id ())) { throw Msg_not_available_exception (); } this->shm_data = static_cast<shm_data_type *> (shm_data); VAPI_DBG ("New Msg<%s>@%p shm_data@%p", vapi_get_msg_name (get_msg_id ()), this, shm_data); } void assign_response (vapi_msg_id_t resp_id, void *shm_data) { assert (nullptr == this->shm_data); if (resp_id != get_msg_id ()) { throw Unexpected_msg_id_exception (); } this->shm_data = static_cast<M *> (shm_data); vapi_swap_to_host<M> (this->shm_data); VAPI_DBG ("Assign response to Msg<%s>@%p shm_data@%p", vapi_get_msg_name (get_msg_id ()), this, shm_data); } std::reference_wrapper<Connection> con; using shm_data_type = M; shm_data_type *shm_data; friend class Connection; template <typename Req, typename Resp, typename... Args> friend class Request; template <typename Req, typename Resp, typename... Args> friend class Dump; template <typename X> friend class Event_registration; template <typename X> friend class Result_set; friend struct Msg_allocator; template <typename X> friend void vapi_msg_set_msg_id (vapi_msg_id_t id); }; /** * Class representing a simple request - with a single response message */ template <typename Req, typename Resp, typename... Args> class Request : public Common_req { public: Request (Connection &con, Args... args, std::function<vapi_error_e (Request<Req, Resp, Args...> &)> callback = nullptr) : Common_req{con}, callback{callback}, request{con, vapi_alloc<Req> (con, args...)}, response{con, nullptr} { } Request (const Request &) = delete; virtual ~Request () { if (RESPONSE_NOT_READY == get_response_state ()) { con.unregister_request (this); } } vapi_error_e execute () { return con.send (this); } const Msg<Req> &get_request (void) const { return request; } const Msg<Resp> &get_response (void) { return response; } private: virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id, void *shm_data) { assert (RESPONSE_NOT_READY == get_response_state ()); response.assign_response (id, shm_data); set_response_state (RESPONSE_READY); if (nullptr != callback) { return std::make_pair (callback (*this), true); } return std::make_pair (VAPI_OK, true); } std::function<vapi_error_e (Request<Req, Resp, Args...> &)> callback; Msg<Req> request; Msg<Resp> response; friend class Connection; }; /** * Class representing iterable set of responses of the same type */ template <typename M> class Result_set { public: ~Result_set () { } Result_set (const Result_set &) = delete; bool is_complete () const { return complete; } size_t size () const { return set.size (); } using const_iterator = typename std::vector<Msg<M>, typename Msg<M>::Msg_allocator>::const_iterator; const_iterator begin () const { return set.begin (); } const_iterator end () const { return set.end (); } void free_response (const_iterator pos) { set.erase (pos); } void free_all_responses () { set.clear (); } private: void mark_complete () { complete = true; } void assign_response (vapi_msg_id_t resp_id, void *shm_data) { if (resp_id != Msg<M>::get_msg_id ()) { { throw Unexpected_msg_id_exception (); } } else if (shm_data) { vapi_swap_to_host<M> (static_cast<M *> (shm_data)); set.emplace_back (con, shm_data); VAPI_DBG ("Result_set@%p emplace_back shm_data@%p", this, shm_data); } } Result_set (Connection &con) : con (con), complete{false} { } Connection &con; bool complete; std::vector<Msg<M>, typename Msg<M>::Msg_allocator> set; template <typename Req, typename Resp, typename... Args> friend class Dump; template <typename X> friend class Event_registration; }; /** * Class representing a dump request - zero or more identical responses to a * single request message */ template <typename Req, typename Resp, typename... Args> class Dump : public Common_req { public: Dump (Connection &con, Args... args, std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback = nullptr) : Common_req{con}, request{con, vapi_alloc<Req> (con, args...)}, result_set{con}, callback{callback} { } Dump (const Dump &) = delete; virtual ~Dump () { } virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id, void *shm_data) { if (id == vapi_msg_id_control_ping_reply) { con.msg_free (shm_data); result_set.mark_complete (); set_response_state (RESPONSE_READY); if (nullptr != callback) { return std::make_pair (callback (*this), true); } return std::make_pair (VAPI_OK, true); } else { result_set.assign_response (id, shm_data); } return std::make_pair (VAPI_OK, false); } vapi_error_e execute () { return con.send_with_control_ping (this); } Msg<Req> &get_request (void) { return request; } using resp_type = typename Msg<Resp>::shm_data_type; const Result_set<Resp> &get_result_set (void) const { return result_set; } private: Msg<Req> request; Result_set<resp_type> result_set; std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback; friend class Connection; }; /** * Class representing event registration - incoming events (messages) from * vpp as a result of a subscription (typically a want_* simple request) */ template <typename M> class Event_registration : public Common_req { public: Event_registration ( Connection &con, std::function<vapi_error_e (Event_registration<M> &)> callback = nullptr) : Common_req{con}, result_set{con}, callback{callback} { if (!con.is_msg_available (M::get_msg_id ())) { throw Msg_not_available_exception (); } con.register_event (this); } Event_registration (const Event_registration &) = delete; virtual ~Event_registration () { con.unregister_event (this); } virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id, void *shm_data) { result_set.assign_response (id, shm_data); if (nullptr != callback) { return std::make_pair (callback (*this), true); } return std::make_pair (VAPI_OK, true); } using resp_type = typename M::shm_data_type; Result_set<resp_type> &get_result_set (void) { return result_set; } private: Result_set<resp_type> result_set; std::function<vapi_error_e (Event_registration<M> &)> callback; }; }; #endif /* * fd.io coding-style-patch-verification: ON * * Local Variables: * eval: (c-set-style "gnu") * End: */