Add C++ API
[vpp.git] / src / vpp-api / vapi / vapi.hpp
1 /*
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:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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  *------------------------------------------------------------------
16  */
17
18 #ifndef vapi_hpp_included
19 #define vapi_hpp_included
20
21 #include <cstddef>
22 #include <vector>
23 #include <mutex>
24 #include <queue>
25 #include <cassert>
26 #include <functional>
27 #include <algorithm>
28 #include <atomic>
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>
34
35 #if VAPI_CPP_DEBUG_LEAKS
36 #include <unordered_set>
37 #endif
38
39 /**
40  * @file
41  * @brief C++ VPP API
42  */
43
44 namespace vapi
45 {
46
47 class Connection;
48
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;
57
58 class Unexpected_msg_id_exception : public std::exception
59 {
60 public:
61   virtual const char *what () const throw ()
62   {
63     return "unexpected message id";
64   }
65 };
66
67 class Msg_not_available_exception : public std::exception
68 {
69 public:
70   virtual const char *what () const throw ()
71   {
72     return "message unavailable";
73   }
74 };
75
76 typedef enum {
77   /** response not ready yet */
78   RESPONSE_NOT_READY,
79
80   /** response to request is ready */
81   RESPONSE_READY,
82
83   /** no response to request (will never come) */
84   RESPONSE_NO_RESPONSE,
85 } vapi_response_state_e;
86
87 /**
88  * Class representing common functionality of a request - response state
89  * and context
90  */
91 class Common_req
92 {
93 public:
94   virtual ~Common_req (){};
95
96   Connection &get_connection ()
97   {
98     return con;
99   };
100
101   vapi_response_state_e get_response_state (void) const
102   {
103     return response_state;
104   }
105
106 private:
107   Connection &con;
108   Common_req (Connection &con) : con{con}, response_state{RESPONSE_NOT_READY}
109   {
110   }
111
112   void set_response_state (vapi_response_state_e state)
113   {
114     response_state = state;
115   }
116
117   virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
118                                                           void *shm_data) = 0;
119
120   void set_context (u32 context)
121   {
122     this->context = context;
123   }
124
125   u32 get_context ()
126   {
127     return context;
128   }
129
130   u32 context;
131   vapi_response_state_e response_state;
132
133   friend class Connection;
134
135   template <typename M> friend class Msg;
136
137   template <typename Req, typename Resp, typename... Args>
138   friend class Request;
139
140   template <typename Req, typename Resp, typename... Args> friend class Dump;
141
142   template <typename M> friend class Event_registration;
143 };
144
145 /**
146  * Class representing a connection to VPP
147  *
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.
151  */
152 class Connection
153 {
154 public:
155   Connection (void) : vapi_ctx{0}, event_count{0}
156   {
157
158     vapi_error_e rv = VAPI_OK;
159     if (!vapi_ctx)
160       {
161         if (VAPI_OK != (rv = vapi_ctx_alloc (&vapi_ctx)))
162           {
163             throw std::bad_alloc ();
164           }
165       }
166     events.reserve (vapi_get_message_count () + 1);
167   }
168
169   Connection (const Connection &) = delete;
170
171   ~Connection (void)
172   {
173     vapi_ctx_free (vapi_ctx);
174 #if VAPI_CPP_DEBUG_LEAKS
175     for (auto x : shm_data_set)
176       {
177         printf ("Leaked shm_data@%p!\n", x);
178       }
179 #endif
180   }
181
182   /**
183    * @brief check if message identified by it's message id is known by the
184    * vpp to which the connection is open
185    */
186   bool is_msg_available (vapi_msg_id_t type)
187   {
188     return vapi_is_msg_available (vapi_ctx, type);
189   }
190
191   /**
192    * @brief connect to vpp
193    *
194    * @param name application name
195    * @param chroot_prefix shared memory prefix
196    * @param max_queued_request max number of outstanding requests queued
197    *
198    * @return VAPI_OK on success, other error code on error
199    */
200   vapi_error_e connect (const char *name, const char *chroot_prefix,
201                         int max_outstanding_requests, int response_queue_size)
202   {
203     return vapi_connect (vapi_ctx, name, chroot_prefix,
204                          max_outstanding_requests, response_queue_size,
205                          VAPI_MODE_BLOCKING);
206   }
207
208   /**
209    * @brief disconnect from vpp
210    *
211    * @return VAPI_OK on success, other error code on error
212    */
213   vapi_error_e disconnect ()
214   {
215     auto x = requests.size ();
216     while (x > 0)
217       {
218         VAPI_DBG ("popping request @%p", requests.front ());
219         requests.pop_front ();
220         --x;
221       }
222     return vapi_disconnect (vapi_ctx);
223   };
224
225   /**
226    * @brief get event file descriptor
227    *
228    * @note this file descriptor becomes readable when messages (from vpp)
229    * are waiting in queue
230    *
231    * @param[out] fd pointer to result variable
232    *
233    * @return VAPI_OK on success, other error code on error
234    */
235   vapi_error_e get_fd (int *fd)
236   {
237     return vapi_get_fd (vapi_ctx, fd);
238   }
239
240   /**
241    * @brief wait for responses from vpp and assign them to appropriate objects
242    *
243    * @param limit stop dispatch after the limit object received it's response
244    *
245    * @return VAPI_OK on success, other error code on error
246    */
247   vapi_error_e dispatch (const Common_req *limit = nullptr)
248   {
249     std::lock_guard<std::mutex> lock (dispatch_mutex);
250     vapi_error_e rv = VAPI_OK;
251     bool loop_again = true;
252     while (loop_again)
253       {
254         void *shm_data;
255         size_t shm_data_size;
256         rv = vapi_recv (vapi_ctx, &shm_data, &shm_data_size);
257         if (VAPI_OK != rv)
258           {
259             return rv;
260           }
261 #if VAPI_CPP_DEBUG_LEAKS
262         on_shm_data_alloc (shm_data);
263 #endif
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;
271         if (has_context)
272           {
273             u32 context = *reinterpret_cast<u32 *> (
274                 (static_cast<u8 *> (shm_data) + vapi_get_context_offset (id)));
275             const auto x = requests.front ();
276             matching_req = x;
277             if (context == x->context)
278               {
279                 std::tie (rv, break_dispatch) =
280                     x->assign_response (id, shm_data);
281               }
282             else
283               {
284                 std::tie (rv, break_dispatch) =
285                     x->assign_response (id, nullptr);
286               }
287             if (break_dispatch)
288               {
289                 requests.pop_front ();
290               }
291           }
292         else
293           {
294             if (events[id])
295               {
296                 std::tie (rv, break_dispatch) =
297                     events[id]->assign_response (id, shm_data);
298                 matching_req = events[id];
299               }
300             else
301               {
302                 msg_free (shm_data);
303               }
304           }
305         if ((matching_req && matching_req == limit && break_dispatch) ||
306             VAPI_OK != rv)
307           {
308             return rv;
309           }
310         loop_again = !requests.empty () || (event_count > 0);
311       }
312     return rv;
313   }
314
315   /**
316    * @brief convenience wrapper function
317    */
318   vapi_error_e dispatch (const Common_req &limit)
319   {
320     return dispatch (&limit);
321   }
322
323   /**
324    * @brief wait for response to a specific request
325    *
326    * @param req request to wait for response for
327    *
328    * @return VAPI_OK on success, other error code on error
329    */
330   vapi_error_e wait_for_response (const Common_req &req)
331   {
332     if (RESPONSE_READY == req.get_response_state ())
333       {
334         return VAPI_OK;
335       }
336     return dispatch (req);
337   }
338
339 private:
340   void msg_free (void *shm_data)
341   {
342 #if VAPI_CPP_DEBUG_LEAKS
343     on_shm_data_free (shm_data);
344 #endif
345     vapi_msg_free (vapi_ctx, shm_data);
346   }
347
348   template <template <typename XReq, typename XResp, typename... XArgs>
349             class X,
350             typename Req, typename Resp, typename... Args>
351   vapi_error_e send (X<Req, Resp, Args...> *req)
352   {
353     if (!req)
354       {
355         return VAPI_EINVAL;
356       }
357     u32 req_context =
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);
363     if (VAPI_OK == rv)
364       {
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);
370 #endif
371         req->request.shm_data = nullptr; /* consumed by vapi_send */
372       }
373     else
374       {
375         vapi_swap_to_host<Req> (req->request.shm_data);
376       }
377     return rv;
378   }
379
380   template <template <typename XReq, typename XResp, typename... XArgs>
381             class X,
382             typename Req, typename Resp, typename... Args>
383   vapi_error_e send_with_control_ping (X<Req, Resp, Args...> *req)
384   {
385     if (!req)
386       {
387         return VAPI_EINVAL;
388       }
389     u32 req_context =
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);
396     if (VAPI_OK == rv)
397       {
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);
403 #endif
404         req->request.shm_data = nullptr; /* consumed by vapi_send */
405       }
406     else
407       {
408         vapi_swap_to_host<Req> (req->request.shm_data);
409       }
410     return rv;
411   }
412
413   void unregister_request (Common_req *request)
414   {
415     std::lock_guard<std::recursive_mutex> lock (requests_mutex);
416     std::remove (requests.begin (), requests.end (), request);
417   }
418
419   template <typename M> void register_event (Event_registration<M> *event)
420   {
421     const vapi_msg_id_t id = M::get_msg_id ();
422     std::lock_guard<std::recursive_mutex> lock (events_mutex);
423     events[id] = event;
424     ++event_count;
425   }
426
427   template <typename M> void unregister_event (Event_registration<M> *event)
428   {
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;
432     --event_count;
433   }
434
435   vapi_ctx_t vapi_ctx;
436   std::atomic_ulong req_context_counter;
437   std::mutex dispatch_mutex;
438
439   std::recursive_mutex requests_mutex;
440   std::recursive_mutex events_mutex;
441   std::deque<Common_req *> requests;
442   std::vector<Common_req *> events;
443   int event_count;
444
445   template <typename Req, typename Resp, typename... Args>
446   friend class Request;
447
448   template <typename Req, typename Resp, typename... Args> friend class Dump;
449
450   template <typename M> friend class Result_set;
451
452   template <typename M> friend class Event_registration;
453
454   template <typename M, typename... Args>
455   friend M *vapi_alloc (Connection &con, Args...);
456
457   template <typename M> friend class Msg;
458
459 #if VAPI_CPP_DEBUG_LEAKS
460   void on_shm_data_alloc (void *shm_data)
461   {
462     if (shm_data)
463       {
464         auto pos = shm_data_set.find (shm_data);
465         if (pos == shm_data_set.end ())
466           {
467             shm_data_set.insert (shm_data);
468           }
469         else
470           {
471             printf ("Double-add shm_data @%p!\n", shm_data);
472           }
473       }
474   }
475
476   void on_shm_data_free (void *shm_data)
477   {
478     auto pos = shm_data_set.find (shm_data);
479     if (pos == shm_data_set.end ())
480       {
481         printf ("Freeing untracked shm_data @%p!\n", shm_data);
482       }
483     else
484       {
485         shm_data_set.erase (pos);
486       }
487   }
488   std::unordered_set<void *> shm_data_set;
489 #endif
490 };
491
492 template <typename Req, typename Resp, typename... Args> class Request;
493
494 template <typename Req, typename Resp, typename... Args> class Dump;
495
496 template <class, class = void> struct vapi_has_payload_trait : std::false_type
497 {
498 };
499
500 template <class... T> using vapi_void_t = void;
501
502 template <class T>
503 struct vapi_has_payload_trait<T, vapi_void_t<decltype (&T::payload)>>
504     : std::true_type
505 {
506 };
507
508 template <typename M> void vapi_msg_set_msg_id (vapi_msg_id_t id)
509 {
510   Msg<M>::set_msg_id (id);
511 }
512
513 /**
514  * Class representing a message stored in shared memory
515  */
516 template <typename M> class Msg
517 {
518 public:
519   Msg (const Msg &) = delete;
520
521   ~Msg ()
522   {
523     VAPI_DBG ("Destroy Msg<%s>@%p, shm_data@%p",
524               vapi_get_msg_name (get_msg_id ()), this, shm_data);
525     if (shm_data)
526       {
527         con.get ().msg_free (shm_data);
528         shm_data = nullptr;
529       }
530   }
531
532   static vapi_msg_id_t get_msg_id ()
533   {
534     return *msg_id_holder ();
535   }
536
537   template <typename X = M>
538   typename std::enable_if<vapi_has_payload_trait<X>::value,
539                           decltype (X::payload) &>::type
540   get_payload () const
541   {
542     return shm_data->payload;
543   }
544
545 private:
546   Msg (Msg<M> &&msg) : con{msg.con}
547   {
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;
552   }
553
554   Msg<M> &operator= (Msg<M> &&msg)
555   {
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);
559     con = msg.con;
560     shm_data = msg.shm_data;
561     msg.shm_data = nullptr;
562     return *this;
563   }
564
565   struct Msg_allocator : std::allocator<Msg<M>>
566   {
567     template <class U, class... Args> void construct (U *p, Args &&... args)
568     {
569       ::new ((void *)p) U (std::forward<Args> (args)...);
570     }
571
572     template <class U> struct rebind
573     {
574       typedef Msg_allocator other;
575     };
576   };
577
578   static void set_msg_id (vapi_msg_id_t id)
579   {
580     assert ((~0 == *msg_id_holder ()) || (id == *msg_id_holder ()));
581     *msg_id_holder () = id;
582   }
583
584   static vapi_msg_id_t *msg_id_holder ()
585   {
586     static vapi_msg_id_t my_id{~0};
587     return &my_id;
588   }
589
590   Msg (Connection &con, void *shm_data) throw (Msg_not_available_exception)
591       : con{con}
592   {
593     if (!con.is_msg_available (get_msg_id ()))
594       {
595         throw Msg_not_available_exception ();
596       }
597     this->shm_data = static_cast<shm_data_type *> (shm_data);
598     VAPI_DBG ("New Msg<%s>@%p shm_data@%p", vapi_get_msg_name (get_msg_id ()),
599               this, shm_data);
600   }
601
602   void assign_response (vapi_msg_id_t resp_id,
603                         void *shm_data) throw (Unexpected_msg_id_exception)
604   {
605     assert (nullptr == this->shm_data);
606     if (resp_id != get_msg_id ())
607       {
608         throw Unexpected_msg_id_exception ();
609       }
610     this->shm_data = static_cast<M *> (shm_data);
611     vapi_swap_to_host<M> (this->shm_data);
612     VAPI_DBG ("Assign response to Msg<%s>@%p shm_data@%p",
613               vapi_get_msg_name (get_msg_id ()), this, shm_data);
614   }
615
616   std::reference_wrapper<Connection> con;
617   using shm_data_type = M;
618   shm_data_type *shm_data;
619
620   friend class Connection;
621
622   template <typename Req, typename Resp, typename... Args>
623   friend class Request;
624
625   template <typename Req, typename Resp, typename... Args> friend class Dump;
626
627   template <typename X> friend class Event_registration;
628
629   template <typename X> friend class Result_set;
630
631   friend struct Msg_allocator;
632
633   template <typename X> friend void vapi_msg_set_msg_id (vapi_msg_id_t id);
634 };
635
636 /**
637  * Class representing a simple request - with a single response message
638  */
639 template <typename Req, typename Resp, typename... Args>
640 class Request : public Common_req
641 {
642 public:
643   Request (Connection &con, Args... args,
644            std::function<vapi_error_e (Request<Req, Resp, Args...> &)>
645                callback = nullptr)
646       : Common_req{con}, callback{callback},
647         request{con, vapi_alloc<Req> (con, args...)}, response{con, nullptr}
648   {
649   }
650
651   Request (const Request &) = delete;
652
653   virtual ~Request ()
654   {
655     if (RESPONSE_NOT_READY == get_response_state ())
656       {
657         con.unregister_request (this);
658       }
659   }
660
661   vapi_error_e execute ()
662   {
663     return con.send (this);
664   }
665
666   const Msg<Req> &get_request (void) const
667   {
668     return request;
669   }
670
671   const Msg<Resp> &get_response (void)
672   {
673     return response;
674   }
675
676 private:
677   virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
678                                                           void *shm_data)
679   {
680     assert (RESPONSE_NOT_READY == get_response_state ());
681     response.assign_response (id, shm_data);
682     set_response_state (RESPONSE_READY);
683     if (nullptr != callback)
684       {
685         return std::make_pair (callback (*this), true);
686       }
687     return std::make_pair (VAPI_OK, true);
688   }
689   std::function<vapi_error_e (Request<Req, Resp, Args...> &)> callback;
690   Msg<Req> request;
691   Msg<Resp> response;
692
693   friend class Connection;
694 };
695
696 /**
697  * Class representing iterable set of responses of the same type
698  */
699 template <typename M> class Result_set
700 {
701 public:
702   ~Result_set ()
703   {
704   }
705
706   Result_set (const Result_set &) = delete;
707
708   bool is_complete () const
709   {
710     return complete;
711   }
712
713   size_t size () const
714   {
715     return set.size ();
716   }
717
718   using const_iterator =
719       typename std::vector<Msg<M>,
720                            typename Msg<M>::Msg_allocator>::const_iterator;
721
722   const_iterator begin () const
723   {
724     return set.begin ();
725   }
726
727   const_iterator end () const
728   {
729     return set.end ();
730   }
731
732   void free_response (const_iterator pos)
733   {
734     set.erase (pos);
735   }
736
737   void free_all_responses ()
738   {
739     set.clear ();
740   }
741
742 private:
743   void mark_complete ()
744   {
745     complete = true;
746   }
747
748   void assign_response (vapi_msg_id_t resp_id,
749                         void *shm_data) throw (Unexpected_msg_id_exception)
750   {
751     if (resp_id != Msg<M>::get_msg_id ())
752       {
753         {
754           throw Unexpected_msg_id_exception ();
755         }
756       }
757     else if (shm_data)
758       {
759         vapi_swap_to_host<M> (static_cast<M *> (shm_data));
760         set.emplace_back (con, shm_data);
761         VAPI_DBG ("Result_set@%p emplace_back shm_data@%p", this, shm_data);
762       }
763   }
764
765   Result_set (Connection &con) : con{con}, complete{false}
766   {
767   }
768
769   Connection &con;
770   bool complete;
771   std::vector<Msg<M>, typename Msg<M>::Msg_allocator> set;
772
773   template <typename Req, typename Resp, typename... Args> friend class Dump;
774
775   template <typename X> friend class Event_registration;
776 };
777
778 /**
779  * Class representing a dump request - zero or more identical responses to a
780  * single request message
781  */
782 template <typename Req, typename Resp, typename... Args>
783 class Dump : public Common_req
784 {
785 public:
786   Dump (Connection &con, Args... args,
787         std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback =
788             nullptr)
789       : Common_req{con}, request{con, vapi_alloc<Req> (con, args...)},
790         result_set{con}, callback{callback}
791   {
792   }
793
794   Dump (const Dump &) = delete;
795
796   virtual ~Dump ()
797   {
798   }
799
800   virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
801                                                           void *shm_data)
802   {
803     if (id == vapi_msg_id_control_ping_reply)
804       {
805         con.msg_free (shm_data);
806         result_set.mark_complete ();
807         set_response_state (RESPONSE_READY);
808         if (nullptr != callback)
809           {
810             return std::make_pair (callback (*this), true);
811           }
812         return std::make_pair (VAPI_OK, true);
813       }
814     else
815       {
816         result_set.assign_response (id, shm_data);
817       }
818     return std::make_pair (VAPI_OK, false);
819   }
820
821   vapi_error_e execute ()
822   {
823     return con.send_with_control_ping (this);
824   }
825
826   Msg<Req> &get_request (void)
827   {
828     return request;
829   }
830
831   using resp_type = typename Msg<Resp>::shm_data_type;
832
833   const Result_set<Resp> &get_result_set (void) const
834   {
835     return result_set;
836   }
837
838 private:
839   Msg<Req> request;
840   Result_set<resp_type> result_set;
841   std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback;
842
843   friend class Connection;
844 };
845
846 /**
847  * Class representing event registration - incoming events (messages) from
848  * vpp as a result of a subscription (typically a want_* simple request)
849  */
850 template <typename M> class Event_registration : public Common_req
851 {
852 public:
853   Event_registration (
854       Connection &con,
855       std::function<vapi_error_e (Event_registration<M> &)> callback =
856           nullptr) throw (Msg_not_available_exception)
857       : Common_req{con}, result_set{con}, callback{callback}
858   {
859     if (!con.is_msg_available (M::get_msg_id ()))
860       {
861         throw Msg_not_available_exception ();
862       }
863     con.register_event (this);
864   }
865
866   Event_registration (const Event_registration &) = delete;
867
868   virtual ~Event_registration ()
869   {
870     con.unregister_event (this);
871   }
872
873   virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
874                                                           void *shm_data)
875   {
876     result_set.assign_response (id, shm_data);
877     if (nullptr != callback)
878       {
879         return std::make_pair (callback (*this), true);
880       }
881     return std::make_pair (VAPI_OK, true);
882   }
883
884   using resp_type = typename M::shm_data_type;
885
886   Result_set<resp_type> &get_result_set (void)
887   {
888     return result_set;
889   }
890
891 private:
892   Result_set<resp_type> result_set;
893   std::function<vapi_error_e (Event_registration<M> &)> callback;
894 };
895 };
896
897 #endif
898
899 /*
900  * fd.io coding-style-patch-verification: ON
901  *
902  * Local Variables:
903  * eval: (c-set-style "gnu")
904  * End:
905  */