Replace static monitor handler tables with dynamic handler registry
[trust_router.git] / mon / mons_handlers.c
1 /*
2  * Copyright (c) 2018, JANET(UK)
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of JANET(UK) nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
25  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
31  * OF THE POSSIBILITY OF SUCH DAMAGE.
32  *
33  */
34
35 /* Handlers for monitoring requests */
36
37 #include <gmodule.h>
38
39 #include <tr_debug.h>
40 #include <mon_internal.h>
41 #include <mons_handlers.h>
42
43
44 /* Static Prototypes */
45 static int dispatch_entry_matches(MONS_DISPATCH_TABLE_ENTRY *e, MON_CMD command, MON_OPT_TYPE opt_type);
46 static MONS_HANDLER_FUNC *mons_find_handler(MONS_INSTANCE *mons, MON_CMD cmd, MON_OPT_TYPE opt_type);
47 static void request_helper(void *element, void *data);
48
49 struct request_helper_data {
50   MON_CMD command;
51   MON_OPT_TYPE opt_type;
52   json_t *payload;
53 };
54
55 /**
56  * Call the appropriate handler for a request
57  *
58  * TODO: report errors from handlers
59  *
60  * @return a MON_RESP structure or null if there was a processing error
61  */
62 MON_RESP *mons_handle_request(TALLOC_CTX *mem_ctx, MONS_INSTANCE *mons, MON_REQ *req)
63 {
64   MON_RESP *resp = NULL;
65   json_t *payload = NULL;
66   struct request_helper_data cookie;
67   size_t ii = 0;
68
69   tr_debug("mons_handle_request: Handling a request");
70
71   /* Start off by allocating our response with a generic error message */
72   resp = mon_resp_new(mem_ctx,
73                       MON_RESP_ERROR,
74                       "Error processing show request",
75                       NULL);
76   if (resp == NULL) {
77     /* we can't respond, just return */
78     tr_crit("mons_handle_request: Error allocating response structure.");
79     goto cleanup;
80   }
81
82   /* Now get a JSON object for our return payload */
83   payload = json_object();
84   if (payload == NULL) {
85     tr_crit("mons_handle_request: Error allocating response payload.");
86     goto cleanup; /* This will return the generic error message set earlier */
87   }
88
89   /* Now call handlers */
90   cookie.command = req->command;
91   cookie.payload = payload; /* borrowed reference */
92
93   if (mon_req_opt_count(req) == 0) {
94     /* call every handler that matches the command */
95     cookie.opt_type = OPT_TYPE_ANY;
96     g_ptr_array_foreach(mons->handlers, request_helper, &cookie);
97   } else {
98     /* call only those handlers that match an option */
99     for (ii=0; ii < mon_req_opt_count(req); ii++) {
100       cookie.opt_type = mon_req_opt_index(req, ii)->type;
101       /* Loop over all handlers - we know we can only have one match for each opt type */
102       g_ptr_array_foreach(mons->handlers, request_helper, &cookie);
103     }
104   }
105
106   /* If we get here, then we successfully processed the request. Return a successful reply. */
107   if (mon_resp_set_message(resp, "success") == 0) {
108     /* Failed to set the response message to success - fail ironically */
109     tr_crit("mons_handle_request: Error setting response message to 'success'.");
110     goto cleanup;
111   }
112
113   /* Attach the accumulated payload to the response */
114   if (json_object_size(payload) > 0)
115     mon_resp_set_payload(resp, payload);
116
117   resp->code = MON_RESP_SUCCESS; /* at last... */
118   tr_debug("mons_handle_request: Successfully processed request.");
119
120 cleanup:
121   if (payload)
122     json_decref(payload);
123   return resp;
124 }
125
126 /**
127  * Register a handler for a command/option combination
128  *
129  * @param mons
130  * @param cmd
131  * @param opt_type
132  * @param f
133  * @param cookie
134  * @return
135  */
136 MON_RC mons_register_handler(MONS_INSTANCE *mons,
137                              MON_CMD cmd,
138                              MON_OPT_TYPE opt_type,
139                              MONS_HANDLER_FUNC *f,
140                              void *cookie)
141 {
142   MONS_DISPATCH_TABLE_ENTRY *entry = NULL;
143
144   if (mons_find_handler(mons, cmd, opt_type) != NULL) {
145     return MON_ERROR;
146   }
147
148   /* Put these in the mons talloc context so we don't have to muck about with
149    * a free function for the GPtrArray */
150   entry = talloc(mons, MONS_DISPATCH_TABLE_ENTRY);
151   if (entry == NULL) {
152     return MON_NOMEM;
153   }
154   entry->command = cmd;
155   entry->opt_type = opt_type;
156   entry->handler = f;
157   entry->cookie = cookie;
158
159   g_ptr_array_add(mons->handlers, entry);
160   return MON_SUCCESS;
161 }
162
163 /**
164  * Two table entries match if none of the commands or opt_types are unknown,
165  * if the commands match, and if the opt types either match or at least one is
166  * OPT_TYPE_ANY.
167  *
168  * No comparison of the handler pointer is included.
169  *
170  * @return 1 if the two match, 0 if not
171  */
172 static int dispatch_entry_matches(MONS_DISPATCH_TABLE_ENTRY *e,
173                                   MON_CMD command,
174                                   MON_OPT_TYPE opt_type)
175 {
176   if ((command == MON_CMD_UNKNOWN) || (opt_type == OPT_TYPE_UNKNOWN))
177     return 0; /* request is invalid */
178
179   if ((e->command == MON_CMD_UNKNOWN) || (e->opt_type == OPT_TYPE_UNKNOWN))
180     return 0; /* e1 is invalid */
181
182   if (e->command != command)
183     return 0; /* commands do not match */
184
185   if (e->opt_type == opt_type)
186     return 1; /* exact match */
187
188   if ( (e->opt_type == OPT_TYPE_ANY) || (opt_type == OPT_TYPE_ANY) )
189     return 1; /* one is a wildcard */
190
191   return 0; /* commands matched but opt_types did not */
192 }
193
194 static MONS_HANDLER_FUNC *mons_find_handler(MONS_INSTANCE *mons, MON_CMD cmd, MON_OPT_TYPE opt_type)
195 {
196   guint index;
197
198   for (index=0; index < mons->handlers->len; index++) {
199     if (dispatch_entry_matches(g_ptr_array_index(mons->handlers, index), cmd, opt_type))
200       return g_ptr_array_index(mons->handlers, index);
201   }
202   return NULL;
203 }
204
205 /**
206  * This calls every request handler that matches a command/opt_type,
207  * gathering their results.
208  *
209  * @param element
210  * @param data
211  */
212 static void request_helper(void *element, void *data)
213 {
214   MONS_DISPATCH_TABLE_ENTRY *entry = talloc_get_type_abort(element, MONS_DISPATCH_TABLE_ENTRY);
215   struct request_helper_data *helper_data = data;
216
217   if (dispatch_entry_matches(entry, helper_data->command, helper_data->opt_type)) {
218     json_object_set(helper_data->payload,
219                     mon_opt_type_to_string(entry->opt_type),
220                     entry->handler(entry->cookie));
221   }
222 }
223