]>
Commit | Line | Data |
---|---|---|
8383d626 MW |
1 | /* |
2 | * Copyright (C) 2014 Martin Willi | |
3 | * Copyright (C) 2014 revosec AG | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms of the GNU General Public License as published by the | |
7 | * Free Software Foundation; either version 2 of the License, or (at your | |
8 | * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, but | |
11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
13 | * for more details. | |
14 | */ | |
15 | ||
16 | #include "vici_dispatcher.h" | |
17 | #include "vici_socket.h" | |
18 | ||
19 | #include <bio/bio_reader.h> | |
20 | #include <bio/bio_writer.h> | |
21 | #include <threading/mutex.h> | |
22 | #include <threading/condvar.h> | |
ecc4b510 | 23 | #include <threading/thread.h> |
8383d626 MW |
24 | #include <collections/array.h> |
25 | #include <collections/hashtable.h> | |
26 | ||
27 | typedef struct private_vici_dispatcher_t private_vici_dispatcher_t; | |
28 | ||
29 | /** | |
30 | * Private data of an vici_dispatcher_t object. | |
31 | */ | |
32 | struct private_vici_dispatcher_t { | |
33 | ||
34 | /** | |
35 | * Public vici_dispatcher_t interface. | |
36 | */ | |
37 | vici_dispatcher_t public; | |
38 | ||
39 | /** | |
40 | * Socket to send/receive messages | |
41 | */ | |
42 | vici_socket_t *socket; | |
43 | ||
44 | /** | |
45 | * List of registered commands (char* => command_t*) | |
46 | */ | |
47 | hashtable_t *cmds; | |
48 | ||
49 | /** | |
50 | * List of known events, and registered clients (char* => event_t*) | |
51 | */ | |
52 | hashtable_t *events; | |
53 | ||
54 | /** | |
55 | * Mutex to lock hashtables | |
56 | */ | |
57 | mutex_t *mutex; | |
58 | ||
59 | /** | |
60 | * Condvar to signal command termination | |
61 | */ | |
62 | condvar_t *cond; | |
63 | }; | |
64 | ||
65 | /** | |
66 | * Registered command | |
67 | */ | |
68 | typedef struct { | |
69 | /** command name */ | |
70 | char *name; | |
71 | /** callback for command */ | |
72 | vici_command_cb_t cb; | |
73 | /** user data to pass to callback */ | |
74 | void *user; | |
75 | /** command currently in use? */ | |
76 | u_int uses; | |
77 | } command_t; | |
78 | ||
79 | /** | |
80 | * Registered event | |
81 | */ | |
82 | typedef struct { | |
83 | /** event name */ | |
84 | char *name; | |
85 | /** registered clients, as u_int */ | |
86 | array_t *clients; | |
87 | /** event currently in use? */ | |
88 | u_int uses; | |
89 | } event_t; | |
90 | ||
91 | /** | |
92 | * Send a operation code, optionally with name and message | |
93 | */ | |
94 | static void send_op(private_vici_dispatcher_t *this, u_int id, | |
95 | vici_operation_t op, char *name, vici_message_t *message) | |
96 | { | |
97 | bio_writer_t *writer; | |
98 | u_int len; | |
99 | ||
100 | len = sizeof(u_int8_t); | |
101 | if (name) | |
102 | { | |
103 | len += sizeof(u_int8_t) + strlen(name); | |
104 | } | |
105 | if (message) | |
106 | { | |
107 | len += message->get_encoding(message).len; | |
108 | } | |
109 | writer = bio_writer_create(len); | |
110 | writer->write_uint8(writer, op); | |
111 | if (name) | |
112 | { | |
113 | writer->write_data8(writer, chunk_from_str(name)); | |
114 | } | |
115 | if (message) | |
116 | { | |
117 | writer->write_data(writer, message->get_encoding(message)); | |
118 | } | |
119 | this->socket->send(this->socket, id, writer->extract_buf(writer)); | |
120 | writer->destroy(writer); | |
121 | } | |
122 | ||
123 | /** | |
124 | * Register client for event | |
125 | */ | |
126 | static void register_event(private_vici_dispatcher_t *this, char *name, | |
127 | u_int id) | |
128 | { | |
129 | event_t *event; | |
130 | ||
131 | this->mutex->lock(this->mutex); | |
132 | event = this->events->get(this->events, name); | |
133 | if (event) | |
134 | { | |
135 | array_insert(event->clients, ARRAY_TAIL, &id); | |
136 | } | |
137 | this->mutex->unlock(this->mutex); | |
138 | ||
139 | if (event) | |
140 | { | |
2676ffdb | 141 | DBG2(DBG_CFG, "vici client %u registered for: %s", id, name); |
8383d626 MW |
142 | send_op(this, id, VICI_EVENT_CONFIRM, NULL, NULL); |
143 | } | |
144 | else | |
145 | { | |
37aa250c | 146 | DBG1(DBG_CFG, "vici client %u invalid registration: %s", id, name); |
8383d626 MW |
147 | send_op(this, id, VICI_EVENT_UNKNOWN, NULL, NULL); |
148 | } | |
149 | } | |
150 | ||
151 | /** | |
152 | * Unregister client for event | |
153 | */ | |
154 | static void unregister_event(private_vici_dispatcher_t *this, char *name, | |
155 | u_int id) | |
156 | { | |
157 | enumerator_t *enumerator; | |
158 | event_t *event; | |
159 | u_int *current; | |
160 | bool found = FALSE; | |
161 | ||
162 | this->mutex->lock(this->mutex); | |
163 | event = this->events->get(this->events, name); | |
164 | if (event) | |
165 | { | |
166 | enumerator = array_create_enumerator(event->clients); | |
167 | while (enumerator->enumerate(enumerator, ¤t)) | |
168 | { | |
169 | if (*current == id) | |
170 | { | |
171 | array_remove_at(event->clients, enumerator); | |
172 | found = TRUE; | |
173 | break; | |
174 | } | |
175 | } | |
176 | enumerator->destroy(enumerator); | |
177 | } | |
178 | this->mutex->unlock(this->mutex); | |
179 | ||
2676ffdb | 180 | DBG2(DBG_CFG, "vici client %u unregistered for: %s", id, name); |
37aa250c | 181 | |
8383d626 MW |
182 | if (found) |
183 | { | |
184 | send_op(this, id, VICI_EVENT_CONFIRM, NULL, NULL); | |
185 | } | |
186 | else | |
187 | { | |
188 | send_op(this, id, VICI_EVENT_UNKNOWN, NULL, NULL); | |
189 | } | |
190 | } | |
191 | ||
ecc4b510 MW |
192 | /** |
193 | * Data to release on thread cancellation | |
194 | */ | |
195 | typedef struct { | |
196 | private_vici_dispatcher_t *this; | |
197 | command_t *cmd; | |
198 | vici_message_t *request; | |
199 | } release_data_t; | |
200 | ||
201 | /** | |
202 | * Release command after execution/cancellation | |
203 | */ | |
204 | CALLBACK(release_command, void, | |
205 | release_data_t *release) | |
206 | { | |
207 | release->request->destroy(release->request); | |
208 | ||
209 | release->this->mutex->lock(release->this->mutex); | |
210 | if (--release->cmd->uses == 0) | |
211 | { | |
212 | release->this->cond->broadcast(release->this->cond); | |
213 | } | |
214 | release->this->mutex->unlock(release->this->mutex); | |
215 | ||
216 | free(release); | |
217 | } | |
218 | ||
8383d626 MW |
219 | /** |
220 | * Process a request message | |
221 | */ | |
222 | void process_request(private_vici_dispatcher_t *this, char *name, u_int id, | |
223 | chunk_t data) | |
224 | { | |
ecc4b510 MW |
225 | vici_message_t *response = NULL; |
226 | release_data_t *release; | |
9bfa397e | 227 | command_t *cmd; |
8383d626 MW |
228 | |
229 | this->mutex->lock(this->mutex); | |
9bfa397e MW |
230 | cmd = this->cmds->get(this->cmds, name); |
231 | if (cmd) | |
8383d626 | 232 | { |
9bfa397e | 233 | cmd->uses++; |
8383d626 MW |
234 | } |
235 | this->mutex->unlock(this->mutex); | |
236 | ||
9bfa397e | 237 | if (cmd) |
8383d626 | 238 | { |
9bfa397e MW |
239 | INIT(release, |
240 | .this = this, | |
241 | .cmd = cmd, | |
242 | ); | |
243 | ||
2676ffdb | 244 | DBG2(DBG_CFG, "vici client %u requests: %s", id, name); |
37aa250c | 245 | |
ecc4b510 MW |
246 | thread_cleanup_push(release_command, release); |
247 | ||
248 | release->request = vici_message_create_from_data(data, FALSE); | |
9bfa397e | 249 | response = release->cmd->cb(cmd->user, cmd->name, id, release->request); |
ecc4b510 MW |
250 | |
251 | thread_cleanup_pop(TRUE); | |
8383d626 | 252 | |
ecc4b510 | 253 | if (response) |
8383d626 | 254 | { |
ecc4b510 MW |
255 | send_op(this, id, VICI_CMD_RESPONSE, NULL, response); |
256 | response->destroy(response); | |
8383d626 | 257 | } |
8383d626 MW |
258 | } |
259 | else | |
260 | { | |
37aa250c | 261 | DBG1(DBG_CFG, "vici client %u invalid request: %s", id, name); |
8383d626 MW |
262 | send_op(this, id, VICI_CMD_UNKNOWN, NULL, NULL); |
263 | } | |
264 | } | |
265 | ||
266 | CALLBACK(inbound, void, | |
267 | private_vici_dispatcher_t *this, u_int id, chunk_t data) | |
268 | { | |
269 | bio_reader_t *reader; | |
270 | chunk_t chunk; | |
271 | u_int8_t type; | |
272 | char name[257]; | |
273 | ||
274 | reader = bio_reader_create(data); | |
275 | if (reader->read_uint8(reader, &type)) | |
276 | { | |
277 | switch (type) | |
278 | { | |
279 | case VICI_EVENT_REGISTER: | |
280 | if (reader->read_data8(reader, &chunk) && | |
281 | vici_stringify(chunk, name, sizeof(name))) | |
282 | { | |
283 | register_event(this, name, id); | |
284 | } | |
285 | else | |
286 | { | |
287 | DBG1(DBG_CFG, "invalid vici register message"); | |
288 | } | |
289 | break; | |
290 | case VICI_EVENT_UNREGISTER: | |
291 | if (reader->read_data8(reader, &chunk) && | |
292 | vici_stringify(chunk, name, sizeof(name))) | |
293 | { | |
294 | unregister_event(this, name, id); | |
295 | } | |
296 | else | |
297 | { | |
298 | DBG1(DBG_CFG, "invalid vici unregister message"); | |
299 | } | |
300 | break; | |
301 | case VICI_CMD_REQUEST: | |
302 | if (reader->read_data8(reader, &chunk) && | |
303 | vici_stringify(chunk, name, sizeof(name))) | |
304 | { | |
ecc4b510 | 305 | thread_cleanup_push((void*)reader->destroy, reader); |
8383d626 | 306 | process_request(this, name, id, reader->peek(reader)); |
ecc4b510 | 307 | thread_cleanup_pop(FALSE); |
8383d626 MW |
308 | } |
309 | else | |
310 | { | |
311 | DBG1(DBG_CFG, "invalid vici request message"); | |
312 | } | |
313 | break; | |
314 | case VICI_CMD_RESPONSE: | |
315 | case VICI_EVENT_CONFIRM: | |
316 | case VICI_EVENT_UNKNOWN: | |
317 | case VICI_EVENT: | |
318 | default: | |
319 | DBG1(DBG_CFG, "unsupported vici operation: %u", type); | |
320 | break; | |
321 | } | |
322 | } | |
323 | else | |
324 | { | |
325 | DBG1(DBG_CFG, "invalid vici message"); | |
326 | } | |
327 | reader->destroy(reader); | |
328 | } | |
329 | ||
330 | CALLBACK(connect_, void, | |
331 | private_vici_dispatcher_t *this, u_int id) | |
332 | { | |
2676ffdb | 333 | DBG2(DBG_CFG, "vici client %u connected", id); |
8383d626 MW |
334 | } |
335 | ||
336 | CALLBACK(disconnect, void, | |
337 | private_vici_dispatcher_t *this, u_int id) | |
338 | { | |
339 | enumerator_t *events, *ids; | |
340 | event_t *event; | |
341 | u_int *current; | |
342 | ||
343 | /* deregister all clients */ | |
344 | this->mutex->lock(this->mutex); | |
345 | events = this->events->create_enumerator(this->events); | |
346 | while (events->enumerate(events, NULL, &event)) | |
347 | { | |
348 | ids = array_create_enumerator(event->clients); | |
349 | while (ids->enumerate(ids, ¤t)) | |
350 | { | |
351 | if (id == *current) | |
352 | { | |
353 | array_remove_at(event->clients, ids); | |
354 | } | |
355 | } | |
356 | ids->destroy(ids); | |
357 | } | |
358 | events->destroy(events); | |
359 | this->mutex->unlock(this->mutex); | |
37aa250c | 360 | |
2676ffdb | 361 | DBG2(DBG_CFG, "vici client %u disconnected", id); |
8383d626 MW |
362 | } |
363 | ||
364 | METHOD(vici_dispatcher_t, manage_command, void, | |
365 | private_vici_dispatcher_t *this, char *name, | |
366 | vici_command_cb_t cb, void *user) | |
367 | { | |
368 | command_t *cmd; | |
369 | ||
370 | this->mutex->lock(this->mutex); | |
371 | if (cb) | |
372 | { | |
373 | INIT(cmd, | |
374 | .name = strdup(name), | |
375 | .cb = cb, | |
376 | .user = user, | |
377 | ); | |
378 | cmd = this->cmds->put(this->cmds, cmd->name, cmd); | |
379 | } | |
380 | else | |
381 | { | |
382 | cmd = this->cmds->remove(this->cmds, name); | |
383 | } | |
384 | if (cmd) | |
385 | { | |
386 | while (cmd->uses) | |
387 | { | |
388 | this->cond->wait(this->cond, this->mutex); | |
389 | } | |
390 | free(cmd->name); | |
391 | free(cmd); | |
392 | } | |
393 | this->mutex->unlock(this->mutex); | |
394 | } | |
395 | ||
396 | METHOD(vici_dispatcher_t, manage_event, void, | |
397 | private_vici_dispatcher_t *this, char *name, bool reg) | |
398 | { | |
399 | event_t *event; | |
400 | ||
401 | this->mutex->lock(this->mutex); | |
402 | if (reg) | |
403 | { | |
404 | INIT(event, | |
405 | .name = strdup(name), | |
406 | .clients = array_create(sizeof(u_int), 0), | |
407 | ); | |
408 | event = this->events->put(this->events, event->name, event); | |
409 | } | |
410 | else | |
411 | { | |
412 | event = this->events->remove(this->events, name); | |
413 | } | |
414 | if (event) | |
415 | { | |
416 | while (event->uses) | |
417 | { | |
418 | this->cond->wait(this->cond, this->mutex); | |
419 | } | |
420 | array_destroy(event->clients); | |
421 | free(event->name); | |
422 | free(event); | |
423 | } | |
424 | this->mutex->unlock(this->mutex); | |
425 | } | |
426 | ||
427 | METHOD(vici_dispatcher_t, raise_event, void, | |
b40a12a9 MW |
428 | private_vici_dispatcher_t *this, char *name, u_int id, |
429 | vici_message_t *message) | |
8383d626 MW |
430 | { |
431 | enumerator_t *enumerator; | |
432 | event_t *event; | |
b40a12a9 | 433 | u_int *current; |
8383d626 MW |
434 | |
435 | this->mutex->lock(this->mutex); | |
436 | event = this->events->get(this->events, name); | |
437 | if (event) | |
438 | { | |
439 | event->uses++; | |
440 | } | |
441 | this->mutex->unlock(this->mutex); | |
442 | ||
443 | enumerator = array_create_enumerator(event->clients); | |
b40a12a9 | 444 | while (enumerator->enumerate(enumerator, ¤t)) |
8383d626 | 445 | { |
b40a12a9 MW |
446 | if (id == 0 || id == *current) |
447 | { | |
448 | send_op(this, *current, VICI_EVENT, name, message); | |
449 | } | |
8383d626 MW |
450 | } |
451 | enumerator->destroy(enumerator); | |
452 | ||
453 | this->mutex->lock(this->mutex); | |
454 | if (--event->uses == 0) | |
455 | { | |
456 | this->cond->broadcast(this->cond); | |
457 | } | |
458 | this->mutex->unlock(this->mutex); | |
459 | ||
460 | message->destroy(message); | |
461 | } | |
462 | ||
463 | METHOD(vici_dispatcher_t, destroy, void, | |
464 | private_vici_dispatcher_t *this) | |
465 | { | |
466 | DESTROY_IF(this->socket); | |
467 | this->mutex->destroy(this->mutex); | |
468 | this->cond->destroy(this->cond); | |
469 | this->cmds->destroy(this->cmds); | |
470 | this->events->destroy(this->events); | |
471 | free(this); | |
472 | } | |
473 | ||
474 | /** | |
475 | * See header | |
476 | */ | |
477 | vici_dispatcher_t *vici_dispatcher_create(char *uri) | |
478 | { | |
479 | private_vici_dispatcher_t *this; | |
480 | ||
481 | INIT(this, | |
482 | .public = { | |
483 | .manage_command = _manage_command, | |
484 | .manage_event = _manage_event, | |
485 | .raise_event = _raise_event, | |
486 | .destroy = _destroy, | |
487 | }, | |
488 | .cmds = hashtable_create(hashtable_hash_str, hashtable_equals_str, 1), | |
489 | .events = hashtable_create(hashtable_hash_str, hashtable_equals_str, 1), | |
490 | .mutex = mutex_create(MUTEX_TYPE_DEFAULT), | |
491 | .cond = condvar_create(CONDVAR_TYPE_DEFAULT), | |
492 | ); | |
493 | ||
494 | this->socket = vici_socket_create(uri, inbound, connect_, disconnect, this); | |
495 | if (!this->socket) | |
496 | { | |
497 | destroy(this); | |
498 | return NULL; | |
499 | } | |
500 | ||
501 | return &this->public; | |
502 | } |