]>
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 | ||
8d96f90a TT |
16 | /* |
17 | * Copyright (C) 2014 Timo Teräs <timo.teras@iki.fi> | |
18 | * | |
19 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
20 | * of this software and associated documentation files (the "Software"), to deal | |
21 | * in the Software without restriction, including without limitation the rights | |
22 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
23 | * copies of the Software, and to permit persons to whom the Software is | |
24 | * furnished to do so, subject to the following conditions: | |
25 | * | |
26 | * The above copyright notice and this permission notice shall be included in | |
27 | * all copies or substantial portions of the Software. | |
28 | * | |
29 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
30 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
31 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
32 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
33 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
34 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
35 | * THE SOFTWARE. | |
36 | */ | |
37 | ||
8383d626 MW |
38 | #include "vici_dispatcher.h" |
39 | #include "vici_socket.h" | |
40 | ||
41 | #include <bio/bio_reader.h> | |
42 | #include <bio/bio_writer.h> | |
43 | #include <threading/mutex.h> | |
44 | #include <threading/condvar.h> | |
ecc4b510 | 45 | #include <threading/thread.h> |
8383d626 MW |
46 | #include <collections/array.h> |
47 | #include <collections/hashtable.h> | |
48 | ||
49 | typedef struct private_vici_dispatcher_t private_vici_dispatcher_t; | |
50 | ||
51 | /** | |
52 | * Private data of an vici_dispatcher_t object. | |
53 | */ | |
54 | struct private_vici_dispatcher_t { | |
55 | ||
56 | /** | |
57 | * Public vici_dispatcher_t interface. | |
58 | */ | |
59 | vici_dispatcher_t public; | |
60 | ||
61 | /** | |
62 | * Socket to send/receive messages | |
63 | */ | |
64 | vici_socket_t *socket; | |
65 | ||
66 | /** | |
67 | * List of registered commands (char* => command_t*) | |
68 | */ | |
69 | hashtable_t *cmds; | |
70 | ||
71 | /** | |
72 | * List of known events, and registered clients (char* => event_t*) | |
73 | */ | |
74 | hashtable_t *events; | |
75 | ||
76 | /** | |
77 | * Mutex to lock hashtables | |
78 | */ | |
79 | mutex_t *mutex; | |
80 | ||
81 | /** | |
82 | * Condvar to signal command termination | |
83 | */ | |
84 | condvar_t *cond; | |
85 | }; | |
86 | ||
87 | /** | |
88 | * Registered command | |
89 | */ | |
90 | typedef struct { | |
91 | /** command name */ | |
92 | char *name; | |
93 | /** callback for command */ | |
94 | vici_command_cb_t cb; | |
95 | /** user data to pass to callback */ | |
96 | void *user; | |
97 | /** command currently in use? */ | |
98 | u_int uses; | |
99 | } command_t; | |
100 | ||
101 | /** | |
102 | * Registered event | |
103 | */ | |
104 | typedef struct { | |
105 | /** event name */ | |
106 | char *name; | |
107 | /** registered clients, as u_int */ | |
108 | array_t *clients; | |
109 | /** event currently in use? */ | |
110 | u_int uses; | |
111 | } event_t; | |
112 | ||
113 | /** | |
114 | * Send a operation code, optionally with name and message | |
115 | */ | |
116 | static void send_op(private_vici_dispatcher_t *this, u_int id, | |
117 | vici_operation_t op, char *name, vici_message_t *message) | |
118 | { | |
119 | bio_writer_t *writer; | |
120 | u_int len; | |
121 | ||
b12c53ce | 122 | len = sizeof(uint8_t); |
8383d626 MW |
123 | if (name) |
124 | { | |
b12c53ce | 125 | len += sizeof(uint8_t) + strlen(name); |
8383d626 MW |
126 | } |
127 | if (message) | |
128 | { | |
129 | len += message->get_encoding(message).len; | |
130 | } | |
131 | writer = bio_writer_create(len); | |
132 | writer->write_uint8(writer, op); | |
133 | if (name) | |
134 | { | |
135 | writer->write_data8(writer, chunk_from_str(name)); | |
136 | } | |
137 | if (message) | |
138 | { | |
139 | writer->write_data(writer, message->get_encoding(message)); | |
140 | } | |
141 | this->socket->send(this->socket, id, writer->extract_buf(writer)); | |
142 | writer->destroy(writer); | |
143 | } | |
144 | ||
145 | /** | |
146 | * Register client for event | |
147 | */ | |
148 | static void register_event(private_vici_dispatcher_t *this, char *name, | |
149 | u_int id) | |
150 | { | |
151 | event_t *event; | |
152 | ||
153 | this->mutex->lock(this->mutex); | |
374511c5 | 154 | while (TRUE) |
8383d626 | 155 | { |
374511c5 MW |
156 | event = this->events->get(this->events, name); |
157 | if (!event) | |
158 | { | |
159 | break; | |
160 | } | |
161 | if (!event->uses) | |
162 | { | |
163 | array_insert(event->clients, ARRAY_TAIL, &id); | |
164 | break; | |
165 | } | |
166 | this->cond->wait(this->cond, this->mutex); | |
8383d626 MW |
167 | } |
168 | this->mutex->unlock(this->mutex); | |
169 | ||
170 | if (event) | |
171 | { | |
2676ffdb | 172 | DBG2(DBG_CFG, "vici client %u registered for: %s", id, name); |
8383d626 MW |
173 | send_op(this, id, VICI_EVENT_CONFIRM, NULL, NULL); |
174 | } | |
175 | else | |
176 | { | |
37aa250c | 177 | DBG1(DBG_CFG, "vici client %u invalid registration: %s", id, name); |
8383d626 MW |
178 | send_op(this, id, VICI_EVENT_UNKNOWN, NULL, NULL); |
179 | } | |
180 | } | |
181 | ||
182 | /** | |
183 | * Unregister client for event | |
184 | */ | |
185 | static void unregister_event(private_vici_dispatcher_t *this, char *name, | |
186 | u_int id) | |
187 | { | |
188 | enumerator_t *enumerator; | |
189 | event_t *event; | |
190 | u_int *current; | |
191 | bool found = FALSE; | |
192 | ||
193 | this->mutex->lock(this->mutex); | |
374511c5 | 194 | while (TRUE) |
8383d626 | 195 | { |
374511c5 MW |
196 | event = this->events->get(this->events, name); |
197 | if (!event) | |
8383d626 | 198 | { |
374511c5 MW |
199 | break; |
200 | } | |
201 | if (!event->uses) | |
202 | { | |
203 | enumerator = array_create_enumerator(event->clients); | |
204 | while (enumerator->enumerate(enumerator, ¤t)) | |
8383d626 | 205 | { |
374511c5 MW |
206 | if (*current == id) |
207 | { | |
208 | array_remove_at(event->clients, enumerator); | |
209 | found = TRUE; | |
210 | break; | |
211 | } | |
8383d626 | 212 | } |
374511c5 MW |
213 | enumerator->destroy(enumerator); |
214 | break; | |
8383d626 | 215 | } |
374511c5 | 216 | this->cond->wait(this->cond, this->mutex); |
8383d626 MW |
217 | } |
218 | this->mutex->unlock(this->mutex); | |
219 | ||
2676ffdb | 220 | DBG2(DBG_CFG, "vici client %u unregistered for: %s", id, name); |
37aa250c | 221 | |
8383d626 MW |
222 | if (found) |
223 | { | |
224 | send_op(this, id, VICI_EVENT_CONFIRM, NULL, NULL); | |
225 | } | |
226 | else | |
227 | { | |
228 | send_op(this, id, VICI_EVENT_UNKNOWN, NULL, NULL); | |
229 | } | |
230 | } | |
231 | ||
ecc4b510 MW |
232 | /** |
233 | * Data to release on thread cancellation | |
234 | */ | |
235 | typedef struct { | |
236 | private_vici_dispatcher_t *this; | |
237 | command_t *cmd; | |
238 | vici_message_t *request; | |
239 | } release_data_t; | |
240 | ||
241 | /** | |
242 | * Release command after execution/cancellation | |
243 | */ | |
244 | CALLBACK(release_command, void, | |
245 | release_data_t *release) | |
246 | { | |
247 | release->request->destroy(release->request); | |
248 | ||
249 | release->this->mutex->lock(release->this->mutex); | |
250 | if (--release->cmd->uses == 0) | |
251 | { | |
252 | release->this->cond->broadcast(release->this->cond); | |
253 | } | |
254 | release->this->mutex->unlock(release->this->mutex); | |
255 | ||
256 | free(release); | |
257 | } | |
258 | ||
8383d626 MW |
259 | /** |
260 | * Process a request message | |
261 | */ | |
262 | void process_request(private_vici_dispatcher_t *this, char *name, u_int id, | |
263 | chunk_t data) | |
264 | { | |
ecc4b510 MW |
265 | vici_message_t *response = NULL; |
266 | release_data_t *release; | |
9bfa397e | 267 | command_t *cmd; |
8383d626 MW |
268 | |
269 | this->mutex->lock(this->mutex); | |
9bfa397e MW |
270 | cmd = this->cmds->get(this->cmds, name); |
271 | if (cmd) | |
8383d626 | 272 | { |
9bfa397e | 273 | cmd->uses++; |
8383d626 MW |
274 | } |
275 | this->mutex->unlock(this->mutex); | |
276 | ||
9bfa397e | 277 | if (cmd) |
8383d626 | 278 | { |
9bfa397e MW |
279 | INIT(release, |
280 | .this = this, | |
281 | .cmd = cmd, | |
282 | ); | |
283 | ||
2676ffdb | 284 | DBG2(DBG_CFG, "vici client %u requests: %s", id, name); |
37aa250c | 285 | |
ecc4b510 MW |
286 | thread_cleanup_push(release_command, release); |
287 | ||
288 | release->request = vici_message_create_from_data(data, FALSE); | |
9bfa397e | 289 | response = release->cmd->cb(cmd->user, cmd->name, id, release->request); |
ecc4b510 MW |
290 | |
291 | thread_cleanup_pop(TRUE); | |
8383d626 | 292 | |
ecc4b510 | 293 | if (response) |
8383d626 | 294 | { |
ecc4b510 MW |
295 | send_op(this, id, VICI_CMD_RESPONSE, NULL, response); |
296 | response->destroy(response); | |
8383d626 | 297 | } |
8383d626 MW |
298 | } |
299 | else | |
300 | { | |
37aa250c | 301 | DBG1(DBG_CFG, "vici client %u invalid request: %s", id, name); |
8383d626 MW |
302 | send_op(this, id, VICI_CMD_UNKNOWN, NULL, NULL); |
303 | } | |
304 | } | |
305 | ||
306 | CALLBACK(inbound, void, | |
307 | private_vici_dispatcher_t *this, u_int id, chunk_t data) | |
308 | { | |
309 | bio_reader_t *reader; | |
310 | chunk_t chunk; | |
b12c53ce | 311 | uint8_t type; |
8383d626 MW |
312 | char name[257]; |
313 | ||
314 | reader = bio_reader_create(data); | |
315 | if (reader->read_uint8(reader, &type)) | |
316 | { | |
317 | switch (type) | |
318 | { | |
319 | case VICI_EVENT_REGISTER: | |
320 | if (reader->read_data8(reader, &chunk) && | |
321 | vici_stringify(chunk, name, sizeof(name))) | |
322 | { | |
323 | register_event(this, name, id); | |
324 | } | |
325 | else | |
326 | { | |
327 | DBG1(DBG_CFG, "invalid vici register message"); | |
328 | } | |
329 | break; | |
330 | case VICI_EVENT_UNREGISTER: | |
331 | if (reader->read_data8(reader, &chunk) && | |
332 | vici_stringify(chunk, name, sizeof(name))) | |
333 | { | |
334 | unregister_event(this, name, id); | |
335 | } | |
336 | else | |
337 | { | |
338 | DBG1(DBG_CFG, "invalid vici unregister message"); | |
339 | } | |
340 | break; | |
341 | case VICI_CMD_REQUEST: | |
342 | if (reader->read_data8(reader, &chunk) && | |
343 | vici_stringify(chunk, name, sizeof(name))) | |
344 | { | |
ecc4b510 | 345 | thread_cleanup_push((void*)reader->destroy, reader); |
8383d626 | 346 | process_request(this, name, id, reader->peek(reader)); |
ecc4b510 | 347 | thread_cleanup_pop(FALSE); |
8383d626 MW |
348 | } |
349 | else | |
350 | { | |
351 | DBG1(DBG_CFG, "invalid vici request message"); | |
352 | } | |
353 | break; | |
354 | case VICI_CMD_RESPONSE: | |
355 | case VICI_EVENT_CONFIRM: | |
356 | case VICI_EVENT_UNKNOWN: | |
357 | case VICI_EVENT: | |
358 | default: | |
359 | DBG1(DBG_CFG, "unsupported vici operation: %u", type); | |
360 | break; | |
361 | } | |
362 | } | |
363 | else | |
364 | { | |
365 | DBG1(DBG_CFG, "invalid vici message"); | |
366 | } | |
367 | reader->destroy(reader); | |
368 | } | |
369 | ||
370 | CALLBACK(connect_, void, | |
371 | private_vici_dispatcher_t *this, u_int id) | |
372 | { | |
2676ffdb | 373 | DBG2(DBG_CFG, "vici client %u connected", id); |
8383d626 MW |
374 | } |
375 | ||
376 | CALLBACK(disconnect, void, | |
377 | private_vici_dispatcher_t *this, u_int id) | |
378 | { | |
379 | enumerator_t *events, *ids; | |
380 | event_t *event; | |
381 | u_int *current; | |
382 | ||
374511c5 | 383 | /* deregister client from all events */ |
8383d626 MW |
384 | this->mutex->lock(this->mutex); |
385 | events = this->events->create_enumerator(this->events); | |
386 | while (events->enumerate(events, NULL, &event)) | |
387 | { | |
374511c5 MW |
388 | while (event->uses) |
389 | { | |
390 | this->cond->wait(this->cond, this->mutex); | |
391 | } | |
8383d626 MW |
392 | ids = array_create_enumerator(event->clients); |
393 | while (ids->enumerate(ids, ¤t)) | |
394 | { | |
395 | if (id == *current) | |
396 | { | |
397 | array_remove_at(event->clients, ids); | |
398 | } | |
399 | } | |
400 | ids->destroy(ids); | |
401 | } | |
402 | events->destroy(events); | |
403 | this->mutex->unlock(this->mutex); | |
37aa250c | 404 | |
2676ffdb | 405 | DBG2(DBG_CFG, "vici client %u disconnected", id); |
8383d626 MW |
406 | } |
407 | ||
408 | METHOD(vici_dispatcher_t, manage_command, void, | |
409 | private_vici_dispatcher_t *this, char *name, | |
410 | vici_command_cb_t cb, void *user) | |
411 | { | |
412 | command_t *cmd; | |
413 | ||
414 | this->mutex->lock(this->mutex); | |
415 | if (cb) | |
416 | { | |
417 | INIT(cmd, | |
418 | .name = strdup(name), | |
419 | .cb = cb, | |
420 | .user = user, | |
421 | ); | |
422 | cmd = this->cmds->put(this->cmds, cmd->name, cmd); | |
423 | } | |
424 | else | |
425 | { | |
426 | cmd = this->cmds->remove(this->cmds, name); | |
427 | } | |
428 | if (cmd) | |
429 | { | |
430 | while (cmd->uses) | |
431 | { | |
432 | this->cond->wait(this->cond, this->mutex); | |
433 | } | |
434 | free(cmd->name); | |
435 | free(cmd); | |
436 | } | |
437 | this->mutex->unlock(this->mutex); | |
438 | } | |
439 | ||
440 | METHOD(vici_dispatcher_t, manage_event, void, | |
441 | private_vici_dispatcher_t *this, char *name, bool reg) | |
442 | { | |
443 | event_t *event; | |
444 | ||
445 | this->mutex->lock(this->mutex); | |
446 | if (reg) | |
447 | { | |
448 | INIT(event, | |
449 | .name = strdup(name), | |
450 | .clients = array_create(sizeof(u_int), 0), | |
451 | ); | |
452 | event = this->events->put(this->events, event->name, event); | |
453 | } | |
454 | else | |
455 | { | |
456 | event = this->events->remove(this->events, name); | |
457 | } | |
458 | if (event) | |
459 | { | |
460 | while (event->uses) | |
461 | { | |
462 | this->cond->wait(this->cond, this->mutex); | |
463 | } | |
464 | array_destroy(event->clients); | |
465 | free(event->name); | |
466 | free(event); | |
467 | } | |
468 | this->mutex->unlock(this->mutex); | |
469 | } | |
470 | ||
8d96f90a TT |
471 | METHOD(vici_dispatcher_t, has_event_listeners, bool, |
472 | private_vici_dispatcher_t *this, char *name) | |
473 | { | |
fa5f6ba2 | 474 | event_t *event; |
8d96f90a TT |
475 | bool retval = FALSE; |
476 | ||
477 | this->mutex->lock(this->mutex); | |
fa5f6ba2 TB |
478 | event = this->events->get(this->events, name); |
479 | if (event) | |
8d96f90a TT |
480 | { |
481 | /* the entry might be getting destroyed, but returning | |
482 | * false positive is not a problem as a later raise_event | |
483 | * will check things again. */ | |
fa5f6ba2 | 484 | retval = array_count(event->clients); |
8d96f90a TT |
485 | } |
486 | this->mutex->unlock(this->mutex); | |
487 | ||
488 | return retval; | |
489 | } | |
490 | ||
8383d626 | 491 | METHOD(vici_dispatcher_t, raise_event, void, |
b40a12a9 MW |
492 | private_vici_dispatcher_t *this, char *name, u_int id, |
493 | vici_message_t *message) | |
8383d626 MW |
494 | { |
495 | enumerator_t *enumerator; | |
496 | event_t *event; | |
b40a12a9 | 497 | u_int *current; |
8383d626 MW |
498 | |
499 | this->mutex->lock(this->mutex); | |
500 | event = this->events->get(this->events, name); | |
501 | if (event) | |
502 | { | |
503 | event->uses++; | |
65cc8f55 | 504 | this->mutex->unlock(this->mutex); |
8383d626 | 505 | |
65cc8f55 MW |
506 | enumerator = array_create_enumerator(event->clients); |
507 | while (enumerator->enumerate(enumerator, ¤t)) | |
b40a12a9 | 508 | { |
65cc8f55 MW |
509 | if (id == 0 || id == *current) |
510 | { | |
511 | send_op(this, *current, VICI_EVENT, name, message); | |
512 | } | |
b40a12a9 | 513 | } |
65cc8f55 | 514 | enumerator->destroy(enumerator); |
8383d626 | 515 | |
65cc8f55 MW |
516 | this->mutex->lock(this->mutex); |
517 | if (--event->uses == 0) | |
518 | { | |
519 | this->cond->broadcast(this->cond); | |
520 | } | |
8383d626 MW |
521 | } |
522 | this->mutex->unlock(this->mutex); | |
523 | ||
524 | message->destroy(message); | |
525 | } | |
526 | ||
527 | METHOD(vici_dispatcher_t, destroy, void, | |
528 | private_vici_dispatcher_t *this) | |
529 | { | |
530 | DESTROY_IF(this->socket); | |
531 | this->mutex->destroy(this->mutex); | |
532 | this->cond->destroy(this->cond); | |
533 | this->cmds->destroy(this->cmds); | |
534 | this->events->destroy(this->events); | |
535 | free(this); | |
536 | } | |
537 | ||
538 | /** | |
539 | * See header | |
540 | */ | |
541 | vici_dispatcher_t *vici_dispatcher_create(char *uri) | |
542 | { | |
543 | private_vici_dispatcher_t *this; | |
544 | ||
545 | INIT(this, | |
546 | .public = { | |
547 | .manage_command = _manage_command, | |
548 | .manage_event = _manage_event, | |
8d96f90a | 549 | .has_event_listeners = _has_event_listeners, |
8383d626 MW |
550 | .raise_event = _raise_event, |
551 | .destroy = _destroy, | |
552 | }, | |
553 | .cmds = hashtable_create(hashtable_hash_str, hashtable_equals_str, 1), | |
554 | .events = hashtable_create(hashtable_hash_str, hashtable_equals_str, 1), | |
555 | .mutex = mutex_create(MUTEX_TYPE_DEFAULT), | |
556 | .cond = condvar_create(CONDVAR_TYPE_DEFAULT), | |
557 | ); | |
558 | ||
559 | this->socket = vici_socket_create(uri, inbound, connect_, disconnect, this); | |
560 | if (!this->socket) | |
561 | { | |
562 | destroy(this); | |
563 | return NULL; | |
564 | } | |
565 | ||
566 | return &this->public; | |
567 | } |