Add C++ API

Change-Id: Iff634f22d43470e2dc028387b3816257fd7b4156
Signed-off-by: Klement Sekera <ksekera@cisco.com>
diff --git a/src/vpp-api/vapi/vapi.hpp b/src/vpp-api/vapi/vapi.hpp
new file mode 100644
index 0000000..3be78b4
--- /dev/null
+++ b/src/vpp-api/vapi/vapi.hpp
@@ -0,0 +1,905 @@
+/*
+ *------------------------------------------------------------------
+ * 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}, 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
+   *
+   * @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)
+  {
+    return vapi_connect (vapi_ctx, name, chroot_prefix,
+                         max_outstanding_requests, response_queue_size,
+                         VAPI_MODE_BLOCKING);
+  }
+
+  /**
+   * @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)
+  {
+    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);
+        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 ((~0 == *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{~0};
+    return &my_id;
+  }
+
+  Msg (Connection &con, void *shm_data) throw (Msg_not_available_exception)
+      : 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) throw (Unexpected_msg_id_exception)
+  {
+    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) throw (Unexpected_msg_id_exception)
+  {
+    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) throw (Msg_not_available_exception)
+      : 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:
+ */