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