build: Update config.guess and config.sub
[asterisk/asterisk.git] / res / res_stasis_device_state.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * Kevin Harwell <kharwell@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*** MODULEINFO
20         <depend type="module">res_stasis</depend>
21         <support_level>core</support_level>
22  ***/
23
24 #include "asterisk.h"
25
26 #include "asterisk/astdb.h"
27 #include "asterisk/astobj2.h"
28 #include "asterisk/module.h"
29 #include "asterisk/stasis_app_impl.h"
30 #include "asterisk/stasis_app_device_state.h"
31
32 #define DEVICE_STATE_SIZE 64
33 /*! astdb family name */
34 #define DEVICE_STATE_FAMILY "StasisDeviceState"
35 /*! Stasis device state provider */
36 #define DEVICE_STATE_PROVIDER_STASIS "Stasis"
37 /*! Scheme for custom device states */
38 #define DEVICE_STATE_SCHEME_STASIS "Stasis:"
39 /*! Scheme for device state subscriptions */
40 #define DEVICE_STATE_SCHEME_SUB "deviceState:"
41
42 /*! Number of hash buckets for device state subscriptions */
43 #define DEVICE_STATE_BUCKETS 37
44
45 /*! The key used for tracking a subscription to all device states */
46 #define DEVICE_STATE_ALL "__AST_DEVICE_STATE_ALL_TOPIC"
47
48 /*! Container for subscribed device states */
49 static struct ao2_container *device_state_subscriptions;
50
51 /*!
52  * \brief Device state subscription object.
53  */
54 struct device_state_subscription {
55         AST_DECLARE_STRING_FIELDS(
56                 AST_STRING_FIELD(app_name);
57                 AST_STRING_FIELD(device_name);
58         );
59         /*! The subscription object */
60         struct stasis_subscription *sub;
61 };
62
63 static int device_state_subscriptions_hash(const void *obj, const int flags)
64 {
65         const struct device_state_subscription *object;
66
67         switch (flags & OBJ_SEARCH_MASK) {
68         case OBJ_SEARCH_OBJECT:
69                 object = obj;
70                 return ast_str_hash(object->device_name);
71         case OBJ_SEARCH_KEY:
72         default:
73                 /* Hash can only work on something with a full key. */
74                 ast_assert(0);
75                 return 0;
76         }
77 }
78
79 static int device_state_subscriptions_cmp(void *obj, void *arg, int flags)
80 {
81         const struct device_state_subscription *object_left = obj;
82         const struct device_state_subscription *object_right = arg;
83         int cmp;
84
85         switch (flags & OBJ_SEARCH_MASK) {
86         case OBJ_SEARCH_OBJECT:
87                 /* find objects matching both device and app names */
88                 if (strcmp(object_left->device_name,
89                            object_right->device_name)) {
90                         return 0;
91                 }
92                 cmp = strcmp(object_left->app_name, object_right->app_name);
93                 break;
94         case OBJ_SEARCH_KEY:
95         case OBJ_SEARCH_PARTIAL_KEY:
96                 ast_assert(0); /* not supported by container */
97                 /* fall through */
98         default:
99                 cmp = 0;
100                 break;
101         }
102
103         return cmp ? 0 : CMP_MATCH | CMP_STOP;
104 }
105
106 static void device_state_subscription_destroy(void *obj)
107 {
108         struct device_state_subscription *sub = obj;
109         ast_string_field_free_memory(sub);
110 }
111
112 static struct device_state_subscription *device_state_subscription_create(
113         const struct stasis_app *app, const char *device_name)
114 {
115         struct device_state_subscription *sub;
116         const char *app_name = stasis_app_name(app);
117         size_t size;
118
119         if (ast_strlen_zero(device_name)) {
120                 device_name = DEVICE_STATE_ALL;
121         }
122
123         size = strlen(device_name) + strlen(app_name) + 2;
124
125         sub = ao2_alloc(sizeof(*sub), device_state_subscription_destroy);
126         if (!sub) {
127                 return NULL;
128         }
129
130         if (ast_string_field_init(sub, size)) {
131                 ao2_ref(sub, -1);
132                 return NULL;
133         }
134
135         ast_string_field_set(sub, app_name, app_name);
136         ast_string_field_set(sub, device_name, device_name);
137         return sub;
138 }
139
140 static struct device_state_subscription *find_device_state_subscription(
141         struct stasis_app *app, const char *name)
142 {
143         struct device_state_subscription dummy_sub = {
144                 .app_name = stasis_app_name(app),
145                 .device_name = name
146         };
147
148         return ao2_find(device_state_subscriptions, &dummy_sub, OBJ_SEARCH_OBJECT | OBJ_NOLOCK);
149 }
150
151 static void remove_device_state_subscription(
152         struct device_state_subscription *sub)
153 {
154         if (sub->sub) {
155                 sub->sub = stasis_unsubscribe_and_join(sub->sub);
156         }
157         ao2_unlink_flags(device_state_subscriptions, sub, OBJ_NOLOCK);
158 }
159
160 struct ast_json *stasis_app_device_state_to_json(
161         const char *name, enum ast_device_state state)
162 {
163         return ast_json_pack("{s: s, s: s}",
164                              "name", name,
165                              "state", ast_devstate_str(state));
166 }
167
168 struct ast_json *stasis_app_device_states_to_json(void)
169 {
170         struct ast_json *array = ast_json_array_create();
171         struct ast_db_entry *tree;
172         struct ast_db_entry *entry;
173
174         tree = ast_db_gettree(DEVICE_STATE_FAMILY, NULL);
175         for (entry = tree; entry; entry = entry->next) {
176                 const char *name = strrchr(entry->key, '/');
177
178                 if (!ast_strlen_zero(name)) {
179                         char device[DEVICE_STATE_SIZE];
180
181                         snprintf(device, sizeof(device), "%s%s", DEVICE_STATE_SCHEME_STASIS, ++name);
182                         ast_json_array_append(array,
183                                 stasis_app_device_state_to_json(device, ast_device_state(device)));
184                 }
185         }
186         ast_db_freetree(tree);
187
188         return array;
189 }
190
191 static void send_device_state(struct device_state_subscription *sub,
192                               const char *name, enum ast_device_state state)
193 {
194         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
195
196         json = ast_json_pack("{s:s, s:s, s:o, s:o}",
197                              "type", "DeviceStateChanged",
198                              "application", sub->app_name,
199                              "timestamp", ast_json_timeval(ast_tvnow(), NULL),
200                              "device_state", stasis_app_device_state_to_json(
201                                      name, state));
202
203         if (!json) {
204                 ast_log(LOG_ERROR, "Unable to create device state json object\n");
205                 return;
206         }
207
208         stasis_app_send(sub->app_name, json);
209 }
210
211 enum stasis_device_state_result stasis_app_device_state_update(
212         const char *name, const char *value)
213 {
214         size_t size = strlen(DEVICE_STATE_SCHEME_STASIS);
215         enum ast_device_state state;
216
217         ast_debug(3, "Updating device name = %s, value = %s", name, value);
218
219         if (strncasecmp(name, DEVICE_STATE_SCHEME_STASIS, size)) {
220                 ast_log(LOG_ERROR, "Update can only be used to set "
221                         "'%s' device state!\n", DEVICE_STATE_SCHEME_STASIS);
222                 return STASIS_DEVICE_STATE_NOT_CONTROLLED;
223         }
224
225         name += size;
226         if (ast_strlen_zero(name)) {
227                 ast_log(LOG_ERROR, "Update requires custom device name!\n");
228                 return STASIS_DEVICE_STATE_MISSING;
229         }
230
231         if (!value || (state = ast_devstate_val(value)) == AST_DEVICE_UNKNOWN) {
232                 ast_log(LOG_ERROR, "Unknown device state "
233                         "value '%s'\n", value);
234                 return STASIS_DEVICE_STATE_UNKNOWN;
235         }
236
237         ast_db_put(DEVICE_STATE_FAMILY, name, value);
238         ast_devstate_changed(state, AST_DEVSTATE_CACHABLE, "%s%s",
239                              DEVICE_STATE_SCHEME_STASIS, name);
240
241         return STASIS_DEVICE_STATE_OK;
242 }
243
244 enum stasis_device_state_result stasis_app_device_state_delete(const char *name)
245 {
246         const char *full_name = name;
247         size_t size = strlen(DEVICE_STATE_SCHEME_STASIS);
248
249         if (strncasecmp(name, DEVICE_STATE_SCHEME_STASIS, size)) {
250                 ast_log(LOG_ERROR, "Can only delete '%s' device states!\n",
251                         DEVICE_STATE_SCHEME_STASIS);
252                 return STASIS_DEVICE_STATE_NOT_CONTROLLED;
253         }
254
255         name += size;
256         if (ast_strlen_zero(name)) {
257                 ast_log(LOG_ERROR, "Delete requires a device name!\n");
258                 return STASIS_DEVICE_STATE_MISSING;
259         }
260
261         if (ast_device_state_clear_cache(full_name)) {
262                 return STASIS_DEVICE_STATE_UNKNOWN;
263         }
264
265         ast_db_del(DEVICE_STATE_FAMILY, name);
266
267         /* send state change for delete */
268         ast_devstate_changed(
269                 AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "%s%s",
270                 DEVICE_STATE_SCHEME_STASIS, name);
271
272         return STASIS_DEVICE_STATE_OK;
273 }
274
275 static void populate_cache(void)
276 {
277         RAII_VAR(struct ast_db_entry *, tree,
278                  ast_db_gettree(DEVICE_STATE_FAMILY, NULL), ast_db_freetree);
279         struct ast_db_entry *entry;
280
281         for (entry = tree; entry; entry = entry->next) {
282                 const char *name = strrchr(entry->key, '/');
283                 if (!ast_strlen_zero(name)) {
284                         ast_devstate_changed(
285                                 ast_devstate_val(entry->data),
286                                 AST_DEVSTATE_CACHABLE, "%s%s\n",
287                                 DEVICE_STATE_SCHEME_STASIS, name + 1);
288                 }
289         }
290 }
291
292 static enum ast_device_state stasis_device_state_cb(const char *data)
293 {
294         char buf[DEVICE_STATE_SIZE];
295
296         ast_db_get(DEVICE_STATE_FAMILY, data, buf, sizeof(buf));
297
298         return ast_devstate_val(buf);
299 }
300
301 static void device_state_cb(void *data, struct stasis_subscription *sub,
302                             struct stasis_message *msg)
303 {
304         struct ast_device_state_message *device_state;
305
306         if (stasis_subscription_final_message(sub, msg)) {
307                 /* Remove stasis subscription's reference to device_state_subscription */
308                 ao2_ref(data, -1);
309                 return;
310         }
311
312         if (ast_device_state_message_type() != stasis_message_type(msg)) {
313                 return;
314         }
315
316         device_state = stasis_message_data(msg);
317         if (device_state->eid) {
318                 /* ignore non-aggregate states */
319                 return;
320         }
321
322         send_device_state(data, device_state->device, device_state->state);
323 }
324
325 static void *find_device_state(const struct stasis_app *app, const char *name)
326 {
327         return device_state_subscription_create(app, name);
328 }
329
330 static int is_subscribed_device_state(struct stasis_app *app, const char *name)
331 {
332         struct device_state_subscription *sub;
333
334         sub = find_device_state_subscription(app, DEVICE_STATE_ALL);
335         if (sub) {
336                 ao2_ref(sub, -1);
337                 return 1;
338         }
339
340         sub = find_device_state_subscription(app, name);
341         if (sub) {
342                 ao2_ref(sub, -1);
343                 return 1;
344         }
345
346         return 0;
347 }
348
349 static int is_subscribed_device_state_lock(struct stasis_app *app, const char *name)
350 {
351         int is_subscribed;
352
353         ao2_lock(device_state_subscriptions);
354         is_subscribed = is_subscribed_device_state(app, name);
355         ao2_unlock(device_state_subscriptions);
356
357         return is_subscribed;
358 }
359
360 static int subscribe_device_state(struct stasis_app *app, void *obj)
361 {
362         struct device_state_subscription *sub = obj;
363         struct stasis_topic *topic;
364
365         if (!sub) {
366                 sub = device_state_subscription_create(app, NULL);
367                 if (!sub) {
368                         return -1;
369                 }
370         }
371
372         if (strcmp(sub->device_name, DEVICE_STATE_ALL)) {
373                 topic = ast_device_state_topic(sub->device_name);
374         } else {
375                 topic = ast_device_state_topic_all();
376         }
377
378         ao2_lock(device_state_subscriptions);
379
380         if (is_subscribed_device_state(app, sub->device_name)) {
381                 ao2_unlock(device_state_subscriptions);
382                 ast_debug(3, "App %s is already subscribed to %s\n", stasis_app_name(app), sub->device_name);
383                 return 0;
384         }
385
386         ast_debug(3, "Subscribing to device %s\n", sub->device_name);
387
388         sub->sub = stasis_subscribe_pool(topic, device_state_cb, ao2_bump(sub));
389         if (!sub->sub) {
390                 ao2_unlock(device_state_subscriptions);
391                 ast_log(LOG_ERROR, "Unable to subscribe to device %s\n",
392                         sub->device_name);
393                 /* Reference we added when attempting to stasis_subscribe_pool */
394                 ao2_ref(sub, -1);
395                 return -1;
396         }
397         stasis_subscription_accept_message_type(sub->sub, ast_device_state_message_type());
398         stasis_subscription_accept_message_type(sub->sub, stasis_subscription_change_type());
399         stasis_subscription_set_filter(sub->sub, STASIS_SUBSCRIPTION_FILTER_SELECTIVE);
400
401         ao2_link_flags(device_state_subscriptions, sub, OBJ_NOLOCK);
402         ao2_unlock(device_state_subscriptions);
403
404         return 0;
405 }
406
407 static int unsubscribe_device_state(struct stasis_app *app, const char *name)
408 {
409         struct device_state_subscription *sub;
410
411         ao2_lock(device_state_subscriptions);
412         sub = find_device_state_subscription(app, name);
413         if (sub) {
414                 remove_device_state_subscription(sub);
415         }
416         ao2_unlock(device_state_subscriptions);
417
418         ao2_cleanup(sub);
419
420         return 0;
421 }
422
423 static int device_to_json_cb(void *obj, void *arg, void *data, int flags)
424 {
425         struct device_state_subscription *sub = obj;
426         const char *app_name = arg;
427         struct ast_json *array = data;
428
429         if (strcmp(sub->app_name, app_name)) {
430                 return 0;
431         }
432
433         ast_json_array_append(
434                 array, ast_json_string_create(sub->device_name));
435         return 0;
436
437 }
438
439 static void devices_to_json(const struct stasis_app *app, struct ast_json *json)
440 {
441         struct ast_json *array = ast_json_array_create();
442         ao2_callback_data(device_state_subscriptions, OBJ_NODATA,
443                           device_to_json_cb, (void *)stasis_app_name(app), array);
444         ast_json_object_set(json, "device_names", array);
445 }
446
447 struct stasis_app_event_source device_state_event_source = {
448         .scheme = DEVICE_STATE_SCHEME_SUB,
449         .find = find_device_state,
450         .subscribe = subscribe_device_state,
451         .unsubscribe = unsubscribe_device_state,
452         .is_subscribed = is_subscribed_device_state_lock,
453         .to_json = devices_to_json
454 };
455
456 static int load_module(void)
457 {
458         populate_cache();
459         if (ast_devstate_prov_add(DEVICE_STATE_PROVIDER_STASIS,
460                                   stasis_device_state_cb)) {
461                 return AST_MODULE_LOAD_DECLINE;
462         }
463
464         device_state_subscriptions = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0,
465                 DEVICE_STATE_BUCKETS, device_state_subscriptions_hash, NULL,
466                 device_state_subscriptions_cmp);
467         if (!device_state_subscriptions) {
468                 ast_devstate_prov_del(DEVICE_STATE_PROVIDER_STASIS);
469                 return AST_MODULE_LOAD_DECLINE;
470         }
471
472         stasis_app_register_event_source(&device_state_event_source);
473         return AST_MODULE_LOAD_SUCCESS;
474 }
475
476 static int unload_module(void)
477 {
478         ast_devstate_prov_del(DEVICE_STATE_PROVIDER_STASIS);
479         stasis_app_unregister_event_source(&device_state_event_source);
480         ao2_cleanup(device_state_subscriptions);
481         device_state_subscriptions = NULL;
482         return 0;
483 }
484
485 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application device state support",
486         .support_level = AST_MODULE_SUPPORT_CORE,
487         .load = load_module,
488         .unload = unload_module,
489         .requires = "res_stasis",
490 );