28357db420c89be2412e3fc5eb7d61452e555e87
[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)
109       : con (con), context{0}, response_state{RESPONSE_NOT_READY}
110   {
111   }
112
113   void set_response_state (vapi_response_state_e state)
114   {
115     response_state = state;
116   }
117
118   virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
119                                                           void *shm_data) = 0;
120
121   void set_context (u32 context)
122   {
123     this->context = context;
124   }
125
126   u32 get_context ()
127   {
128     return context;
129   }
130
131   u32 context;
132   vapi_response_state_e response_state;
133
134   friend class Connection;
135
136   template <typename M> friend class Msg;
137
138   template <typename Req, typename Resp, typename... Args>
139   friend class Request;
140
141   template <typename Req, typename Resp, typename... Args> friend class Dump;
142
143   template <typename M> friend class Event_registration;
144 };
145
146 /**
147  * Class representing a connection to VPP
148  *
149  * After creating a Connection object, call connect() to actually connect
150  * to VPP. Use is_msg_available to discover whether a specific message is known
151  * and supported by the VPP connected to.
152  */
153 class Connection
154 {
155 public:
156   Connection (void) : vapi_ctx{0}, event_count{0}
157   {
158
159     vapi_error_e rv = VAPI_OK;
160     if (!vapi_ctx)
161       {
162         if (VAPI_OK != (rv = vapi_ctx_alloc (&vapi_ctx)))
163           {
164             throw std::bad_alloc ();
165           }
166       }
167     events.reserve (vapi_get_message_count () + 1);
168   }
169
170   Connection (const Connection &) = delete;
171
172   ~Connection (void)
173   {
174     vapi_ctx_free (vapi_ctx);
175 #if VAPI_CPP_DEBUG_LEAKS
176     for (auto x : shm_data_set)
177       {
178         printf ("Leaked shm_data@%p!\n", x);
179       }
180 #endif
181   }
182
183   /**
184    * @brief check if message identified by it's message id is known by the
185    * vpp to which the connection is open
186    */
187   bool is_msg_available (vapi_msg_id_t type)
188   {
189     return vapi_is_msg_available (vapi_ctx, type);
190   }
191
192   /**
193    * @brief connect to vpp
194    *
195    * @param name application name
196    * @param chroot_prefix shared memory prefix
197    * @param max_queued_request max number of outstanding requests queued
198    *
199    * @return VAPI_OK on success, other error code on error
200    */
201   vapi_error_e connect (const char *name, const char *chroot_prefix,
202                         int max_outstanding_requests, int response_queue_size)
203   {
204     return vapi_connect (vapi_ctx, name, chroot_prefix,
205                          max_outstanding_requests, response_queue_size,
206                          VAPI_MODE_BLOCKING);
207   }
208
209   /**
210    * @brief disconnect from vpp
211    *
212    * @return VAPI_OK on success, other error code on error
213    */
214   vapi_error_e disconnect ()
215   {
216     auto x = requests.size ();
217     while (x > 0)
218       {
219         VAPI_DBG ("popping request @%p", requests.front ());
220         requests.pop_front ();
221         --x;
222       }
223     return vapi_disconnect (vapi_ctx);
224   };
225
226   /**
227    * @brief get event file descriptor
228    *
229    * @note this file descriptor becomes readable when messages (from vpp)
230    * are waiting in queue
231    *
232    * @param[out] fd pointer to result variable
233    *
234    * @return VAPI_OK on success, other error code on error
235    */
236   vapi_error_e get_fd (int *fd)
237   {
238     return vapi_get_fd (vapi_ctx, fd);
239   }
240
241   /**
242    * @brief wait for responses from vpp and assign them to appropriate objects
243    *
244    * @param limit stop dispatch after the limit object received it's response
245    *
246    * @return VAPI_OK on success, other error code on error
247    */
248   vapi_error_e dispatch (const Common_req *limit = nullptr, u32 time = 5)
249   {
250     std::lock_guard<std::mutex> lock (dispatch_mutex);
251     vapi_error_e rv = VAPI_OK;
252     bool loop_again = true;
253     while (loop_again)
254       {
255         void *shm_data;
256         size_t shm_data_size;
257         rv = vapi_recv (vapi_ctx, &shm_data, &shm_data_size, SVM_Q_TIMEDWAIT,
258                         time);
259         if (VAPI_OK != rv)
260           {
261             return rv;
262           }
263 #if VAPI_CPP_DEBUG_LEAKS
264         on_shm_data_alloc (shm_data);
265 #endif
266         std::lock_guard<std::recursive_mutex> requests_lock (requests_mutex);
267         std::lock_guard<std::recursive_mutex> events_lock (events_mutex);
268         vapi_msg_id_t id = vapi_lookup_vapi_msg_id_t (
269             vapi_ctx, be16toh (*static_cast<u16 *> (shm_data)));
270         bool has_context = vapi_msg_is_with_context (id);
271         bool break_dispatch = false;
272         Common_req *matching_req = nullptr;
273         if (has_context)
274           {
275             u32 context = *reinterpret_cast<u32 *> (
276                 (static_cast<u8 *> (shm_data) + vapi_get_context_offset (id)));
277             const auto x = requests.front ();
278             matching_req = x;
279             if (context == x->context)
280               {
281                 std::tie (rv, break_dispatch) =
282                     x->assign_response (id, shm_data);
283               }
284             else
285               {
286                 std::tie (rv, break_dispatch) =
287                     x->assign_response (id, nullptr);
288               }
289             if (break_dispatch)
290               {
291                 requests.pop_front ();
292               }
293           }
294         else
295           {
296             if (events[id])
297               {
298                 std::tie (rv, break_dispatch) =
299                     events[id]->assign_response (id, shm_data);
300                 matching_req = events[id];
301               }
302             else
303               {
304                 msg_free (shm_data);
305               }
306           }
307         if ((matching_req && matching_req == limit && break_dispatch) ||
308             VAPI_OK != rv)
309           {
310             return rv;
311           }
312         loop_again = !requests.empty () || (event_count > 0);
313       }
314     return rv;
315   }
316
317   /**
318    * @brief convenience wrapper function
319    */
320   vapi_error_e dispatch (const Common_req &limit)
321   {
322     return dispatch (&limit);
323   }
324
325   /**
326    * @brief wait for response to a specific request
327    *
328    * @param req request to wait for response for
329    *
330    * @return VAPI_OK on success, other error code on error
331    */
332   vapi_error_e wait_for_response (const Common_req &req)
333   {
334     if (RESPONSE_READY == req.get_response_state ())
335       {
336         return VAPI_OK;
337       }
338     return dispatch (req);
339   }
340
341 private:
342   void msg_free (void *shm_data)
343   {
344 #if VAPI_CPP_DEBUG_LEAKS
345     on_shm_data_free (shm_data);
346 #endif
347     vapi_msg_free (vapi_ctx, shm_data);
348   }
349
350   template <template <typename XReq, typename XResp, typename... XArgs>
351             class X,
352             typename Req, typename Resp, typename... Args>
353   vapi_error_e send (X<Req, Resp, Args...> *req)
354   {
355     if (!req)
356       {
357         return VAPI_EINVAL;
358       }
359     u32 req_context =
360         req_context_counter.fetch_add (1, std::memory_order_relaxed);
361     req->request.shm_data->header.context = req_context;
362     vapi_swap_to_be<Req> (req->request.shm_data);
363     std::lock_guard<std::recursive_mutex> lock (requests_mutex);
364     vapi_error_e rv = vapi_send (vapi_ctx, req->request.shm_data);
365     if (VAPI_OK == rv)
366       {
367         VAPI_DBG ("Push %p", req);
368         requests.emplace_back (req);
369         req->set_context (req_context);
370 #if VAPI_CPP_DEBUG_LEAKS
371         on_shm_data_free (req->request.shm_data);
372 #endif
373         req->request.shm_data = nullptr; /* consumed by vapi_send */
374       }
375     else
376       {
377         vapi_swap_to_host<Req> (req->request.shm_data);
378       }
379     return rv;
380   }
381
382   template <template <typename XReq, typename XResp, typename... XArgs>
383             class X,
384             typename Req, typename Resp, typename... Args>
385   vapi_error_e send_with_control_ping (X<Req, Resp, Args...> *req)
386   {
387     if (!req)
388       {
389         return VAPI_EINVAL;
390       }
391     u32 req_context =
392         req_context_counter.fetch_add (1, std::memory_order_relaxed);
393     req->request.shm_data->header.context = req_context;
394     vapi_swap_to_be<Req> (req->request.shm_data);
395     std::lock_guard<std::recursive_mutex> lock (requests_mutex);
396     vapi_error_e rv = vapi_send_with_control_ping (
397         vapi_ctx, req->request.shm_data, req_context);
398     if (VAPI_OK == rv)
399       {
400         VAPI_DBG ("Push %p", req);
401         requests.emplace_back (req);
402         req->set_context (req_context);
403 #if VAPI_CPP_DEBUG_LEAKS
404         on_shm_data_free (req->request.shm_data);
405 #endif
406         req->request.shm_data = nullptr; /* consumed by vapi_send */
407       }
408     else
409       {
410         vapi_swap_to_host<Req> (req->request.shm_data);
411       }
412     return rv;
413   }
414
415   void unregister_request (Common_req *request)
416   {
417     std::lock_guard<std::recursive_mutex> lock (requests_mutex);
418     std::remove (requests.begin (), requests.end (), request);
419   }
420
421   template <typename M> void register_event (Event_registration<M> *event)
422   {
423     const vapi_msg_id_t id = M::get_msg_id ();
424     std::lock_guard<std::recursive_mutex> lock (events_mutex);
425     events[id] = event;
426     ++event_count;
427   }
428
429   template <typename M> void unregister_event (Event_registration<M> *event)
430   {
431     const vapi_msg_id_t id = M::get_msg_id ();
432     std::lock_guard<std::recursive_mutex> lock (events_mutex);
433     events[id] = nullptr;
434     --event_count;
435   }
436
437   vapi_ctx_t vapi_ctx;
438   std::atomic_ulong req_context_counter;
439   std::mutex dispatch_mutex;
440
441   std::recursive_mutex requests_mutex;
442   std::recursive_mutex events_mutex;
443   std::deque<Common_req *> requests;
444   std::vector<Common_req *> events;
445   int event_count;
446
447   template <typename Req, typename Resp, typename... Args>
448   friend class Request;
449
450   template <typename Req, typename Resp, typename... Args> friend class Dump;
451
452   template <typename M> friend class Result_set;
453
454   template <typename M> friend class Event_registration;
455
456   template <typename M, typename... Args>
457   friend M *vapi_alloc (Connection &con, Args...);
458
459   template <typename M> friend class Msg;
460
461 #if VAPI_CPP_DEBUG_LEAKS
462   void on_shm_data_alloc (void *shm_data)
463   {
464     if (shm_data)
465       {
466         auto pos = shm_data_set.find (shm_data);
467         if (pos == shm_data_set.end ())
468           {
469             shm_data_set.insert (shm_data);
470           }
471         else
472           {
473             printf ("Double-add shm_data @%p!\n", shm_data);
474           }
475       }
476   }
477
478   void on_shm_data_free (void *shm_data)
479   {
480     auto pos = shm_data_set.find (shm_data);
481     if (pos == shm_data_set.end ())
482       {
483         printf ("Freeing untracked shm_data @%p!\n", shm_data);
484       }
485     else
486       {
487         shm_data_set.erase (pos);
488       }
489   }
490   std::unordered_set<void *> shm_data_set;
491 #endif
492 };
493
494 template <typename Req, typename Resp, typename... Args> class Request;
495
496 template <typename Req, typename Resp, typename... Args> class Dump;
497
498 template <class, class = void> struct vapi_has_payload_trait : std::false_type
499 {
500 };
501
502 template <class... T> using vapi_void_t = void;
503
504 template <class T>
505 struct vapi_has_payload_trait<T, vapi_void_t<decltype (&T::payload)>>
506     : std::true_type
507 {
508 };
509
510 template <typename M> void vapi_msg_set_msg_id (vapi_msg_id_t id)
511 {
512   Msg<M>::set_msg_id (id);
513 }
514
515 /**
516  * Class representing a message stored in shared memory
517  */
518 template <typename M> class Msg
519 {
520 public:
521   Msg (const Msg &) = delete;
522
523   ~Msg ()
524   {
525     VAPI_DBG ("Destroy Msg<%s>@%p, shm_data@%p",
526               vapi_get_msg_name (get_msg_id ()), this, shm_data);
527     if (shm_data)
528       {
529         con.get ().msg_free (shm_data);
530         shm_data = nullptr;
531       }
532   }
533
534   static vapi_msg_id_t get_msg_id ()
535   {
536     return *msg_id_holder ();
537   }
538
539   template <typename X = M>
540   typename std::enable_if<vapi_has_payload_trait<X>::value,
541                           decltype (X::payload) &>::type
542   get_payload () const
543   {
544     return shm_data->payload;
545   }
546
547 private:
548   Msg (Msg<M> &&msg) : con{msg.con}
549   {
550     VAPI_DBG ("Move construct Msg<%s> from msg@%p to msg@%p, shm_data@%p",
551               vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data);
552     shm_data = msg.shm_data;
553     msg.shm_data = nullptr;
554   }
555
556   Msg<M> &operator= (Msg<M> &&msg)
557   {
558     VAPI_DBG ("Move assign Msg<%s> from msg@%p to msg@%p, shm_data@%p",
559               vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data);
560     con.get ().msg_free (shm_data);
561     con = msg.con;
562     shm_data = msg.shm_data;
563     msg.shm_data = nullptr;
564     return *this;
565   }
566
567   struct Msg_allocator : std::allocator<Msg<M>>
568   {
569     template <class U, class... Args> void construct (U *p, Args &&... args)
570     {
571       ::new ((void *)p) U (std::forward<Args> (args)...);
572     }
573
574     template <class U> struct rebind
575     {
576       typedef Msg_allocator other;
577     };
578   };
579
580   static void set_msg_id (vapi_msg_id_t id)
581   {
582     assert ((INVALID_MSG_ID == *msg_id_holder ()) ||
583             (id == *msg_id_holder ()));
584     *msg_id_holder () = id;
585   }
586
587   static vapi_msg_id_t *msg_id_holder ()
588   {
589     static vapi_msg_id_t my_id{INVALID_MSG_ID};
590     return &my_id;
591   }
592
593   Msg (Connection &con, void *shm_data) : con{con}
594   {
595     if (!con.is_msg_available (get_msg_id ()))
596       {
597         throw Msg_not_available_exception ();
598       }
599     this->shm_data = static_cast<shm_data_type *> (shm_data);
600     VAPI_DBG ("New Msg<%s>@%p shm_data@%p", vapi_get_msg_name (get_msg_id ()),
601               this, shm_data);
602   }
603
604   void assign_response (vapi_msg_id_t resp_id, void *shm_data)
605   {
606     assert (nullptr == this->shm_data);
607     if (resp_id != get_msg_id ())
608       {
609         throw Unexpected_msg_id_exception ();
610       }
611     this->shm_data = static_cast<M *> (shm_data);
612     vapi_swap_to_host<M> (this->shm_data);
613     VAPI_DBG ("Assign response to Msg<%s>@%p shm_data@%p",
614               vapi_get_msg_name (get_msg_id ()), this, shm_data);
615   }
616
617   std::reference_wrapper<Connection> con;
618   using shm_data_type = M;
619   shm_data_type *shm_data;
620
621   friend class Connection;
622
623   template <typename Req, typename Resp, typename... Args>
624   friend class Request;
625
626   template <typename Req, typename Resp, typename... Args> friend class Dump;
627
628   template <typename X> friend class Event_registration;
629
630   template <typename X> friend class Result_set;
631
632   friend struct Msg_allocator;
633
634   template <typename X> friend void vapi_msg_set_msg_id (vapi_msg_id_t id);
635 };
636
637 /**
638  * Class representing a simple request - with a single response message
639  */
640 template <typename Req, typename Resp, typename... Args>
641 class Request : public Common_req
642 {
643 public:
644   Request (Connection &con, Args... args,
645            std::function<vapi_error_e (Request<Req, Resp, Args...> &)>
646                callback = nullptr)
647       : Common_req{con}, callback{callback},
648         request{con, vapi_alloc<Req> (con, args...)}, response{con, nullptr}
649   {
650   }
651
652   Request (const Request &) = delete;
653
654   virtual ~Request ()
655   {
656     if (RESPONSE_NOT_READY == get_response_state ())
657       {
658         con.unregister_request (this);
659       }
660   }
661
662   vapi_error_e execute ()
663   {
664     return con.send (this);
665   }
666
667   const Msg<Req> &get_request (void) const
668   {
669     return request;
670   }
671
672   const Msg<Resp> &get_response (void)
673   {
674     return response;
675   }
676
677 private:
678   virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
679                                                           void *shm_data)
680   {
681     assert (RESPONSE_NOT_READY == get_response_state ());
682     response.assign_response (id, shm_data);
683     set_response_state (RESPONSE_READY);
684     if (nullptr != callback)
685       {
686         return std::make_pair (callback (*this), true);
687       }
688     return std::make_pair (VAPI_OK, true);
689   }
690   std::function<vapi_error_e (Request<Req, Resp, Args...> &)> callback;
691   Msg<Req> request;
692   Msg<Resp> response;
693
694   friend class Connection;
695 };
696
697 /**
698  * Class representing iterable set of responses of the same type
699  */
700 template <typename M> class Result_set
701 {
702 public:
703   ~Result_set ()
704   {
705   }
706
707   Result_set (const Result_set &) = delete;
708
709   bool is_complete () const
710   {
711     return complete;
712   }
713
714   size_t size () const
715   {
716     return set.size ();
717   }
718
719   using const_iterator =
720       typename std::vector<Msg<M>,
721                            typename Msg<M>::Msg_allocator>::const_iterator;
722
723   const_iterator begin () const
724   {
725     return set.begin ();
726   }
727
728   const_iterator end () const
729   {
730     return set.end ();
731   }
732
733   void free_response (const_iterator pos)
734   {
735     set.erase (pos);
736   }
737
738   void free_all_responses ()
739   {
740     set.clear ();
741   }
742
743 private:
744   void mark_complete ()
745   {
746     complete = true;
747   }
748
749   void assign_response (vapi_msg_id_t resp_id, void *shm_data)
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 = nullptr)
856       : Common_req{con}, result_set{con}, callback{callback}
857   {
858     if (!con.is_msg_available (M::get_msg_id ()))
859       {
860         throw Msg_not_available_exception ();
861       }
862     con.register_event (this);
863   }
864
865   Event_registration (const Event_registration &) = delete;
866
867   virtual ~Event_registration ()
868   {
869     con.unregister_event (this);
870   }
871
872   virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
873                                                           void *shm_data)
874   {
875     result_set.assign_response (id, shm_data);
876     if (nullptr != callback)
877       {
878         return std::make_pair (callback (*this), true);
879       }
880     return std::make_pair (VAPI_OK, true);
881   }
882
883   using resp_type = typename M::shm_data_type;
884
885   Result_set<resp_type> &get_result_set (void)
886   {
887     return result_set;
888   }
889
890 private:
891   Result_set<resp_type> result_set;
892   std::function<vapi_error_e (Event_registration<M> &)> callback;
893 };
894 };
895
896 #endif
897
898 /*
899  * fd.io coding-style-patch-verification: ON
900  *
901  * Local Variables:
902  * eval: (c-set-style "gnu")
903  * End:
904  */