e0fd2e5051c41223a58bc1f93b305d2fbcf230e8
[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) : con{con}
591   {
592     if (!con.is_msg_available (get_msg_id ()))
593       {
594         throw Msg_not_available_exception ();
595       }
596     this->shm_data = static_cast<shm_data_type *> (shm_data);
597     VAPI_DBG ("New Msg<%s>@%p shm_data@%p", vapi_get_msg_name (get_msg_id ()),
598               this, shm_data);
599   }
600
601   void assign_response (vapi_msg_id_t resp_id, void *shm_data)
602   {
603     assert (nullptr == this->shm_data);
604     if (resp_id != get_msg_id ())
605       {
606         throw Unexpected_msg_id_exception ();
607       }
608     this->shm_data = static_cast<M *> (shm_data);
609     vapi_swap_to_host<M> (this->shm_data);
610     VAPI_DBG ("Assign response to Msg<%s>@%p shm_data@%p",
611               vapi_get_msg_name (get_msg_id ()), this, shm_data);
612   }
613
614   std::reference_wrapper<Connection> con;
615   using shm_data_type = M;
616   shm_data_type *shm_data;
617
618   friend class Connection;
619
620   template <typename Req, typename Resp, typename... Args>
621   friend class Request;
622
623   template <typename Req, typename Resp, typename... Args> friend class Dump;
624
625   template <typename X> friend class Event_registration;
626
627   template <typename X> friend class Result_set;
628
629   friend struct Msg_allocator;
630
631   template <typename X> friend void vapi_msg_set_msg_id (vapi_msg_id_t id);
632 };
633
634 /**
635  * Class representing a simple request - with a single response message
636  */
637 template <typename Req, typename Resp, typename... Args>
638 class Request : public Common_req
639 {
640 public:
641   Request (Connection &con, Args... args,
642            std::function<vapi_error_e (Request<Req, Resp, Args...> &)>
643                callback = nullptr)
644       : Common_req{con}, callback{callback},
645         request{con, vapi_alloc<Req> (con, args...)}, response{con, nullptr}
646   {
647   }
648
649   Request (const Request &) = delete;
650
651   virtual ~Request ()
652   {
653     if (RESPONSE_NOT_READY == get_response_state ())
654       {
655         con.unregister_request (this);
656       }
657   }
658
659   vapi_error_e execute ()
660   {
661     return con.send (this);
662   }
663
664   const Msg<Req> &get_request (void) const
665   {
666     return request;
667   }
668
669   const Msg<Resp> &get_response (void)
670   {
671     return response;
672   }
673
674 private:
675   virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
676                                                           void *shm_data)
677   {
678     assert (RESPONSE_NOT_READY == get_response_state ());
679     response.assign_response (id, shm_data);
680     set_response_state (RESPONSE_READY);
681     if (nullptr != callback)
682       {
683         return std::make_pair (callback (*this), true);
684       }
685     return std::make_pair (VAPI_OK, true);
686   }
687   std::function<vapi_error_e (Request<Req, Resp, Args...> &)> callback;
688   Msg<Req> request;
689   Msg<Resp> response;
690
691   friend class Connection;
692 };
693
694 /**
695  * Class representing iterable set of responses of the same type
696  */
697 template <typename M> class Result_set
698 {
699 public:
700   ~Result_set ()
701   {
702   }
703
704   Result_set (const Result_set &) = delete;
705
706   bool is_complete () const
707   {
708     return complete;
709   }
710
711   size_t size () const
712   {
713     return set.size ();
714   }
715
716   using const_iterator =
717       typename std::vector<Msg<M>,
718                            typename Msg<M>::Msg_allocator>::const_iterator;
719
720   const_iterator begin () const
721   {
722     return set.begin ();
723   }
724
725   const_iterator end () const
726   {
727     return set.end ();
728   }
729
730   void free_response (const_iterator pos)
731   {
732     set.erase (pos);
733   }
734
735   void free_all_responses ()
736   {
737     set.clear ();
738   }
739
740 private:
741   void mark_complete ()
742   {
743     complete = true;
744   }
745
746   void assign_response (vapi_msg_id_t resp_id, void *shm_data)
747   {
748     if (resp_id != Msg<M>::get_msg_id ())
749       {
750         {
751           throw Unexpected_msg_id_exception ();
752         }
753       }
754     else if (shm_data)
755       {
756         vapi_swap_to_host<M> (static_cast<M *> (shm_data));
757         set.emplace_back (con, shm_data);
758         VAPI_DBG ("Result_set@%p emplace_back shm_data@%p", this, shm_data);
759       }
760   }
761
762   Result_set (Connection &con) : con (con), complete{false}
763   {
764   }
765
766   Connection &con;
767   bool complete;
768   std::vector<Msg<M>, typename Msg<M>::Msg_allocator> set;
769
770   template <typename Req, typename Resp, typename... Args> friend class Dump;
771
772   template <typename X> friend class Event_registration;
773 };
774
775 /**
776  * Class representing a dump request - zero or more identical responses to a
777  * single request message
778  */
779 template <typename Req, typename Resp, typename... Args>
780 class Dump : public Common_req
781 {
782 public:
783   Dump (Connection &con, Args... args,
784         std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback =
785             nullptr)
786       : Common_req{con}, request{con, vapi_alloc<Req> (con, args...)},
787         result_set{con}, callback{callback}
788   {
789   }
790
791   Dump (const Dump &) = delete;
792
793   virtual ~Dump ()
794   {
795   }
796
797   virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
798                                                           void *shm_data)
799   {
800     if (id == vapi_msg_id_control_ping_reply)
801       {
802         con.msg_free (shm_data);
803         result_set.mark_complete ();
804         set_response_state (RESPONSE_READY);
805         if (nullptr != callback)
806           {
807             return std::make_pair (callback (*this), true);
808           }
809         return std::make_pair (VAPI_OK, true);
810       }
811     else
812       {
813         result_set.assign_response (id, shm_data);
814       }
815     return std::make_pair (VAPI_OK, false);
816   }
817
818   vapi_error_e execute ()
819   {
820     return con.send_with_control_ping (this);
821   }
822
823   Msg<Req> &get_request (void)
824   {
825     return request;
826   }
827
828   using resp_type = typename Msg<Resp>::shm_data_type;
829
830   const Result_set<Resp> &get_result_set (void) const
831   {
832     return result_set;
833   }
834
835 private:
836   Msg<Req> request;
837   Result_set<resp_type> result_set;
838   std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback;
839
840   friend class Connection;
841 };
842
843 /**
844  * Class representing event registration - incoming events (messages) from
845  * vpp as a result of a subscription (typically a want_* simple request)
846  */
847 template <typename M> class Event_registration : public Common_req
848 {
849 public:
850   Event_registration (
851       Connection &con,
852       std::function<vapi_error_e (Event_registration<M> &)> callback = nullptr)
853       : Common_req{con}, result_set{con}, callback{callback}
854   {
855     if (!con.is_msg_available (M::get_msg_id ()))
856       {
857         throw Msg_not_available_exception ();
858       }
859     con.register_event (this);
860   }
861
862   Event_registration (const Event_registration &) = delete;
863
864   virtual ~Event_registration ()
865   {
866     con.unregister_event (this);
867   }
868
869   virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
870                                                           void *shm_data)
871   {
872     result_set.assign_response (id, shm_data);
873     if (nullptr != callback)
874       {
875         return std::make_pair (callback (*this), true);
876       }
877     return std::make_pair (VAPI_OK, true);
878   }
879
880   using resp_type = typename M::shm_data_type;
881
882   Result_set<resp_type> &get_result_set (void)
883   {
884     return result_set;
885   }
886
887 private:
888   Result_set<resp_type> result_set;
889   std::function<vapi_error_e (Event_registration<M> &)> callback;
890 };
891 };
892
893 #endif
894
895 /*
896  * fd.io coding-style-patch-verification: ON
897  *
898  * Local Variables:
899  * eval: (c-set-style "gnu")
900  * End:
901  */