stasis: Add internal filtering of messages.
[asterisk/asterisk.git] / main / stasis_message.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * David M. Lee, II <dlee@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 /*! \file
20  *
21  * \brief Stasis Message API.
22  *
23  * \author David M. Lee, II <dlee@digium.com>
24  */
25
26 /*** MODULEINFO
27         <support_level>core</support_level>
28  ***/
29
30 #include "asterisk.h"
31
32 #include "asterisk/astobj2.h"
33 #include "asterisk/stasis.h"
34 #include "asterisk/utils.h"
35 #include "asterisk/hashtab.h"
36
37 /*! \internal */
38 struct stasis_message_type {
39         struct stasis_message_vtable *vtable;
40         char *name;
41         unsigned int hash;
42         int id;
43 };
44
45 static struct stasis_message_vtable null_vtable = {};
46 static int message_type_id;
47
48 static void message_type_dtor(void *obj)
49 {
50         struct stasis_message_type *type = obj;
51         ast_free(type->name);
52         type->name = NULL;
53 }
54
55 int stasis_message_type_create(const char *name,
56         struct stasis_message_vtable *vtable,
57         struct stasis_message_type **result)
58 {
59         struct stasis_message_type *type;
60
61         /* Check for declination */
62         if (name && stasis_message_type_declined(name)) {
63                 return STASIS_MESSAGE_TYPE_DECLINED;
64         }
65
66         type = ao2_t_alloc_options(sizeof(*type), message_type_dtor,
67                 AO2_ALLOC_OPT_LOCK_NOLOCK, name ?: "");
68         if (!type) {
69                 return STASIS_MESSAGE_TYPE_ERROR;
70         }
71         if (!vtable) {
72                 /* Null object pattern, FTW! */
73                 vtable = &null_vtable;
74         }
75
76         type->name = ast_strdup(name);
77         if (!type->name) {
78                 ao2_cleanup(type);
79                 return STASIS_MESSAGE_TYPE_ERROR;
80         }
81         type->hash = ast_hashtab_hash_string(name);
82         type->vtable = vtable;
83         type->id = ast_atomic_fetchadd_int(&message_type_id, +1);
84         *result = type;
85
86         return STASIS_MESSAGE_TYPE_SUCCESS;
87 }
88
89 const char *stasis_message_type_name(const struct stasis_message_type *type)
90 {
91         return type->name;
92 }
93
94 unsigned int stasis_message_type_hash(const struct stasis_message_type *type)
95 {
96         return type->hash;
97 }
98
99 int stasis_message_type_id(const struct stasis_message_type *type)
100 {
101         return type->id;
102 }
103
104 /*! \internal */
105 struct stasis_message {
106         /*! Time the message was created */
107         struct timeval timestamp;
108         /*! Type of the message */
109         struct stasis_message_type *type;
110         /*! Where this message originated.  NULL if aggregate message. */
111         const struct ast_eid *eid_ptr;
112         /*! Message content */
113         void *data;
114         /*! Where this message originated. */
115         struct ast_eid eid;
116 };
117
118 static void stasis_message_dtor(void *obj)
119 {
120         struct stasis_message *message = obj;
121         ao2_cleanup(message->data);
122 }
123
124 struct stasis_message *stasis_message_create_full(struct stasis_message_type *type, void *data, const struct ast_eid *eid)
125 {
126         struct stasis_message *message;
127
128         if (type == NULL || data == NULL) {
129                 return NULL;
130         }
131
132         message = ao2_t_alloc_options(sizeof(*message), stasis_message_dtor,
133                 AO2_ALLOC_OPT_LOCK_NOLOCK, type->name);
134         if (message == NULL) {
135                 return NULL;
136         }
137
138         message->timestamp = ast_tvnow();
139         /*
140          * XXX Normal ao2 ref counting rules says we should increment the message
141          * type ref here and decrement it in stasis_message_dtor().  However, the
142          * stasis message could be cached and legitimately cause the type ref count
143          * to hit the excessive ref count assertion.  Since the message type
144          * practically has to be a global object anyway, we can get away with not
145          * holding a ref in the stasis message.
146          */
147         message->type = type;
148         ao2_ref(data, +1);
149         message->data = data;
150         if (eid) {
151                 message->eid_ptr = &message->eid;
152                 message->eid = *eid;
153         }
154
155         return message;
156 }
157
158 struct stasis_message *stasis_message_create(struct stasis_message_type *type, void *data)
159 {
160         return stasis_message_create_full(type, data, &ast_eid_default);
161 }
162
163 const struct ast_eid *stasis_message_eid(const struct stasis_message *msg)
164 {
165         if (msg == NULL) {
166                 return NULL;
167         }
168         return msg->eid_ptr;
169 }
170
171 struct stasis_message_type *stasis_message_type(const struct stasis_message *msg)
172 {
173         if (msg == NULL) {
174                 return NULL;
175         }
176         return msg->type;
177 }
178
179 void *stasis_message_data(const struct stasis_message *msg)
180 {
181         if (msg == NULL) {
182                 return NULL;
183         }
184         return msg->data;
185 }
186
187 const struct timeval *stasis_message_timestamp(const struct stasis_message *msg)
188 {
189         if (msg == NULL) {
190                 return NULL;
191         }
192         return &msg->timestamp;
193 }
194
195 #define INVOKE_VIRTUAL(fn, ...)                                 \
196         ({                                                                                      \
197                 if (!msg) {                                                             \
198                         return NULL;                                            \
199                 }                                                                               \
200                 ast_assert(msg->type != NULL);                  \
201                 ast_assert(msg->type->vtable != NULL);  \
202                 if (!msg->type->vtable->fn) {                   \
203                         return NULL;                                            \
204                 }                                                                               \
205                 msg->type->vtable->fn(__VA_ARGS__);             \
206         })
207
208 struct ast_manager_event_blob *stasis_message_to_ami(struct stasis_message *msg)
209 {
210         return INVOKE_VIRTUAL(to_ami, msg);
211 }
212
213 struct ast_json *stasis_message_to_json(
214         struct stasis_message *msg,
215         struct stasis_message_sanitizer *sanitize)
216 {
217         return INVOKE_VIRTUAL(to_json, msg, sanitize);
218 }
219
220 struct ast_event *stasis_message_to_event(struct stasis_message *msg)
221 {
222         return INVOKE_VIRTUAL(to_event, msg);
223 }
224
225 #define HAS_VIRTUAL(fn, msg)                                    \
226         ({                                                                                      \
227                 if (!msg) {                                                             \
228                         return 0;                                                       \
229                 }                                                                               \
230                 ast_assert(msg->type != NULL);                  \
231                 ast_assert(msg->type->vtable != NULL);  \
232                 !!msg->type->vtable->fn;                                \
233         })
234
235 int stasis_message_can_be_ami(struct stasis_message *msg)
236 {
237         return HAS_VIRTUAL(to_ami, msg);
238 }