Merge "stasis: Allow filtering by formatter"
[asterisk/asterisk.git] / funcs / func_cdr.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999-2006, Digium, Inc.
5  *
6  * Portions Copyright (C) 2005, Anthony Minessale II
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  Call Detail Record related dialplan functions
22  *
23  * \author Anthony Minessale II
24  *
25  * \ingroup functions
26  */
27
28 /*** MODULEINFO
29         <support_level>core</support_level>
30  ***/
31
32 #include "asterisk.h"
33
34 #include "asterisk/module.h"
35 #include "asterisk/channel.h"
36 #include "asterisk/pbx.h"
37 #include "asterisk/utils.h"
38 #include "asterisk/app.h"
39 #include "asterisk/cdr.h"
40 #include "asterisk/stasis.h"
41 #include "asterisk/stasis_message_router.h"
42
43 /*** DOCUMENTATION
44         <function name="CDR" language="en_US">
45                 <synopsis>
46                         Gets or sets a CDR variable.
47                 </synopsis>
48                 <syntax>
49                         <parameter name="name" required="true">
50                                 <para>CDR field name:</para>
51                                 <enumlist>
52                                         <enum name="clid">
53                                                 <para>Caller ID.</para>
54                                         </enum>
55                                         <enum name="lastdata">
56                                                 <para>Last application arguments.</para>
57                                         </enum>
58                                         <enum name="disposition">
59                                                 <para>The final state of the CDR.</para>
60                                                 <enumlist>
61                                                         <enum name="0">
62                                                                 <para><literal>NO ANSWER</literal></para>
63                                                         </enum>
64                                                         <enum name="1">
65                                                                 <para><literal>NO ANSWER</literal> (NULL record)</para>
66                                                         </enum>
67                                                         <enum name="2">
68                                                                 <para><literal>FAILED</literal></para>
69                                                         </enum>
70                                                         <enum name="4">
71                                                                 <para><literal>BUSY</literal></para>
72                                                         </enum>
73                                                         <enum name="8">
74                                                                 <para><literal>ANSWERED</literal></para>
75                                                         </enum>
76                                                         <enum name="16">
77                                                                 <para><literal>CONGESTION</literal></para>
78                                                         </enum>
79                                                 </enumlist>
80                                         </enum>
81                                         <enum name="src">
82                                                 <para>Source.</para>
83                                         </enum>
84                                         <enum name="start">
85                                                 <para>Time the call started.</para>
86                                         </enum>
87                                         <enum name="amaflags">
88                                                 <para>R/W the Automatic Message Accounting (AMA) flags on the channel.
89                                                 When read from a channel, the integer value will always be returned.
90                                                 When written to a channel, both the string format or integer value
91                                                 is accepted.</para>
92                                                 <enumlist>
93                                                         <enum name="1"><para><literal>OMIT</literal></para></enum>
94                                                         <enum name="2"><para><literal>BILLING</literal></para></enum>
95                                                         <enum name="3"><para><literal>DOCUMENTATION</literal></para></enum>
96                                                 </enumlist>
97                                                 <warning><para>Accessing this setting is deprecated in CDR. Please use the CHANNEL function instead.</para></warning>
98                                         </enum>
99                                         <enum name="dst">
100                                                 <para>Destination.</para>
101                                         </enum>
102                                         <enum name="answer">
103                                                 <para>Time the call was answered.</para>
104                                         </enum>
105                                         <enum name="accountcode">
106                                                 <para>The channel's account code.</para>
107                                                 <warning><para>Accessing this setting is deprecated in CDR. Please use the CHANNEL function instead.</para></warning>
108                                         </enum>
109                                         <enum name="dcontext">
110                                                 <para>Destination context.</para>
111                                         </enum>
112                                         <enum name="end">
113                                                 <para>Time the call ended.</para>
114                                         </enum>
115                                         <enum name="uniqueid">
116                                                 <para>The channel's unique id.</para>
117                                         </enum>
118                                         <enum name="dstchannel">
119                                                 <para>Destination channel.</para>
120                                         </enum>
121                                         <enum name="duration">
122                                                 <para>Duration of the call.</para>
123                                         </enum>
124                                         <enum name="userfield">
125                                                 <para>The channel's user specified field.</para>
126                                         </enum>
127                                         <enum name="lastapp">
128                                                 <para>Last application.</para>
129                                         </enum>
130                                         <enum name="billsec">
131                                                 <para>Duration of the call once it was answered.</para>
132                                         </enum>
133                                         <enum name="channel">
134                                                 <para>Channel name.</para>
135                                         </enum>
136                                         <enum name="sequence">
137                                                 <para>CDR sequence number.</para>
138                                         </enum>
139                                 </enumlist>
140                         </parameter>
141                         <parameter name="options" required="false">
142                                 <optionlist>
143                                         <option name="f">
144                                                 <para>Returns billsec or duration fields as floating point values.</para>
145                                         </option>
146                                         <option name="u">
147                                                 <para>Retrieves the raw, unprocessed value.</para>
148                                                 <para>For example, 'start', 'answer', and 'end' will be retrieved as epoch
149                                                 values, when the <literal>u</literal> option is passed, but formatted as YYYY-MM-DD HH:MM:SS
150                                                 otherwise.  Similarly, disposition and amaflags will return their raw
151                                                 integral values.</para>
152                                         </option>
153                                 </optionlist>
154                         </parameter>
155                 </syntax>
156                 <description>
157                         <para>All of the CDR field names are read-only, except for <literal>accountcode</literal>,
158                         <literal>userfield</literal>, and <literal>amaflags</literal>. You may, however, supply
159                         a name not on the above list, and create your own variable, whose value can be changed
160                         with this function, and this variable will be stored on the CDR.</para>
161                         <note><para>CDRs can only be modified before the bridge between two channels is
162                         torn down. For example, CDRs may not be modified after the <literal>Dial</literal>
163                         application has returned.</para></note>
164                         <para>Example: exten => 1,1,Set(CDR(userfield)=test)</para>
165                 </description>
166         </function>
167         <function name="CDR_PROP" language="en_US">
168                 <synopsis>
169                         Set a property on a channel's CDR.
170                 </synopsis>
171                 <syntax>
172                         <parameter name="name" required="true">
173                                 <para>The property to set on the CDR.</para>
174                                 <enumlist>
175                                         <enum name="party_a">
176                                                 <para>Set this channel as the preferred Party A when
177                                                 channels are associated together.</para>
178                                                 <para>Write-Only</para>
179                                         </enum>
180                                         <enum name="disable">
181                                                 <para>Setting to 1 will disable CDRs for this channel.
182                                                 Setting to 0 will enable CDRs for this channel.</para>
183                                                 <para>Write-Only</para>
184                                         </enum>
185                                 </enumlist>
186                         </parameter>
187                 </syntax>
188                 <description>
189                         <para>This function sets a property on a channel's CDR. Properties
190                         alter the behavior of how the CDR operates for that channel.</para>
191                 </description>
192         </function>
193  ***/
194
195 enum cdr_option_flags {
196         OPT_UNPARSED = (1 << 1),
197         OPT_FLOAT = (1 << 2),
198 };
199
200 AST_APP_OPTIONS(cdr_func_options, {
201         AST_APP_OPTION('f', OPT_FLOAT),
202         AST_APP_OPTION('u', OPT_UNPARSED),
203 });
204
205 struct cdr_func_payload {
206         struct ast_channel *chan;
207         const char *cmd;
208         const char *arguments;
209         const char *value;
210         void *data;
211 };
212
213 struct cdr_func_data {
214         char *buf;
215         size_t len;
216 };
217
218 STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_read_message_type);
219 STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_write_message_type);
220 STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_prop_write_message_type);
221
222 static struct timeval cdr_retrieve_time(struct ast_channel *chan, const char *time_name)
223 {
224         struct timeval time = { 0 };
225         char *value = NULL;
226         char tempbuf[128];
227         long int tv_sec;
228         long int tv_usec;
229
230         if (ast_strlen_zero(ast_channel_name(chan))) {
231                 /* Format request on a dummy channel */
232                 ast_cdr_format_var(ast_channel_cdr(chan), time_name, &value, tempbuf, sizeof(tempbuf), 1);
233         } else {
234                 ast_cdr_getvar(ast_channel_name(chan), time_name, tempbuf, sizeof(tempbuf));
235         }
236
237         /* time.tv_usec is suseconds_t, which could be int or long */
238         if (sscanf(tempbuf, "%ld.%ld", &tv_sec, &tv_usec) == 2) {
239                 time.tv_sec = tv_sec;
240                 time.tv_usec = tv_usec;
241         } else {
242                 ast_log(AST_LOG_WARNING, "Failed to fully extract '%s' from CDR\n", time_name);
243         }
244
245         return time;
246 }
247
248 static void cdr_read_callback(void *data, struct stasis_subscription *sub, struct stasis_message *message)
249 {
250         struct cdr_func_payload *payload = stasis_message_data(message);
251         struct cdr_func_data *output;
252         char *info;
253         char *value = NULL;
254         struct ast_flags flags = { 0 };
255         char tempbuf[512];
256         AST_DECLARE_APP_ARGS(args,
257                 AST_APP_ARG(variable);
258                 AST_APP_ARG(options);
259         );
260
261         if (cdr_read_message_type() != stasis_message_type(message)) {
262                 return;
263         }
264
265         ast_assert(payload != NULL);
266         output = payload->data;
267         ast_assert(output != NULL);
268
269         if (ast_strlen_zero(payload->arguments)) {
270                 ast_log(AST_LOG_WARNING, "%s requires a variable (%s(variable[,option]))\n)",
271                         payload->cmd, payload->cmd);
272                 return;
273         }
274         info = ast_strdupa(payload->arguments);
275         AST_STANDARD_APP_ARGS(args, info);
276
277         if (!ast_strlen_zero(args.options)) {
278                 ast_app_parse_options(cdr_func_options, &flags, NULL, args.options);
279         }
280
281         if (ast_strlen_zero(ast_channel_name(payload->chan))) {
282                 /* Format request on a dummy channel */
283                 ast_cdr_format_var(ast_channel_cdr(payload->chan), args.variable, &value, tempbuf, sizeof(tempbuf), ast_test_flag(&flags, OPT_UNPARSED));
284                 if (ast_strlen_zero(value)) {
285                         return;
286                 }
287                 ast_copy_string(tempbuf, value, sizeof(tempbuf));
288                 ast_set_flag(&flags, OPT_UNPARSED);
289         } else if (ast_cdr_getvar(ast_channel_name(payload->chan), args.variable, tempbuf, sizeof(tempbuf))) {
290                 return;
291         }
292
293         if (ast_test_flag(&flags, OPT_FLOAT)
294                 && (!strcasecmp("billsec", args.variable) || !strcasecmp("duration", args.variable))) {
295                 struct timeval start = cdr_retrieve_time(payload->chan, !strcasecmp("billsec", args.variable) ? "answer" : "start");
296                 struct timeval finish = cdr_retrieve_time(payload->chan, "end");
297                 double delta;
298
299                 if (ast_tvzero(finish)) {
300                         finish = ast_tvnow();
301                 }
302
303                 if (ast_tvzero(start)) {
304                         delta = 0.0;
305                 } else {
306                         delta = (double)(ast_tvdiff_us(finish, start) / 1000000.0);
307                 }
308                 snprintf(tempbuf, sizeof(tempbuf), "%lf", delta);
309
310         } else if (!ast_test_flag(&flags, OPT_UNPARSED)) {
311                 if (!strcasecmp("start", args.variable)
312                         || !strcasecmp("end", args.variable)
313                         || !strcasecmp("answer", args.variable)) {
314                         struct timeval fmt_time;
315                         struct ast_tm tm;
316                         /* tv_usec is suseconds_t, which could be int or long */
317                         long int tv_sec;
318                         long int tv_usec;
319
320                         if (sscanf(tempbuf, "%ld.%ld", &tv_sec, &tv_usec) != 2) {
321                                 ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
322                                         args.variable, tempbuf, ast_channel_name(payload->chan));
323                                 return;
324                         }
325                         if (tv_sec) {
326                                 fmt_time.tv_sec = tv_sec;
327                                 fmt_time.tv_usec = tv_usec;
328                                 ast_localtime(&fmt_time, &tm, NULL);
329                                 ast_strftime(tempbuf, sizeof(tempbuf), "%Y-%m-%d %T", &tm);
330                         } else {
331                                 tempbuf[0] = '\0';
332                         }
333                 } else if (!strcasecmp("disposition", args.variable)) {
334                         int disposition;
335
336                         if (sscanf(tempbuf, "%8d", &disposition) != 1) {
337                                 ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
338                                         args.variable, tempbuf, ast_channel_name(payload->chan));
339                                 return;
340                         }
341                         snprintf(tempbuf, sizeof(tempbuf), "%s", ast_cdr_disp2str(disposition));
342                 } else if (!strcasecmp("amaflags", args.variable)) {
343                         int amaflags;
344
345                         if (sscanf(tempbuf, "%8d", &amaflags) != 1) {
346                                 ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
347                                         args.variable, tempbuf, ast_channel_name(payload->chan));
348                                 return;
349                         }
350                         snprintf(tempbuf, sizeof(tempbuf), "%s", ast_channel_amaflags2string(amaflags));
351                 }
352         }
353
354         ast_copy_string(output->buf, tempbuf, output->len);
355 }
356
357 static void cdr_write_callback(void *data, struct stasis_subscription *sub, struct stasis_message *message)
358 {
359         struct cdr_func_payload *payload;
360         struct ast_flags flags = { 0 };
361         AST_DECLARE_APP_ARGS(args,
362                 AST_APP_ARG(variable);
363                 AST_APP_ARG(options);
364         );
365         char *parse;
366
367         if (cdr_write_message_type() != stasis_message_type(message)) {
368                 return;
369         }
370         payload = stasis_message_data(message);
371         if (!payload) {
372                 return;
373         }
374         if (ast_strlen_zero(payload->arguments)
375                 || !payload->value) {
376                 /* Sanity check.  cdr_write() could never send these bad messages */
377                 ast_assert(0);
378                 return;
379         }
380
381         parse = ast_strdupa(payload->arguments);
382         AST_STANDARD_APP_ARGS(args, parse);
383
384         if (!ast_strlen_zero(args.options)) {
385                 ast_app_parse_options(cdr_func_options, &flags, NULL, args.options);
386         }
387
388         /* These are already handled by cdr_write() */
389         ast_assert(strcasecmp(args.variable, "accountcode")
390                 && strcasecmp(args.variable, "peeraccount")
391                 && strcasecmp(args.variable, "amaflags"));
392
393         if (!strcasecmp(args.variable, "userfield")) {
394                 ast_cdr_setuserfield(ast_channel_name(payload->chan), payload->value);
395         } else {
396                 ast_cdr_setvar(ast_channel_name(payload->chan), args.variable, payload->value);
397         }
398 }
399
400 static void cdr_prop_write_callback(void *data, struct stasis_subscription *sub, struct stasis_message *message)
401 {
402         struct cdr_func_payload *payload = stasis_message_data(message);
403         enum ast_cdr_options option;
404         char *parse;
405         AST_DECLARE_APP_ARGS(args,
406                 AST_APP_ARG(variable);
407                 AST_APP_ARG(options);
408         );
409
410         if (cdr_prop_write_message_type() != stasis_message_type(message)) {
411                 return;
412         }
413
414         if (!payload) {
415                 return;
416         }
417
418         if (ast_strlen_zero(payload->arguments)) {
419                 ast_log(AST_LOG_WARNING, "%s requires a variable (%s(variable)=value)\n)",
420                         payload->cmd, payload->cmd);
421                 return;
422         }
423         if (ast_strlen_zero(payload->value)) {
424                 ast_log(AST_LOG_WARNING, "%s requires a value (%s(variable)=value)\n)",
425                         payload->cmd, payload->cmd);
426                 return;
427         }
428         parse = ast_strdupa(payload->arguments);
429         AST_STANDARD_APP_ARGS(args, parse);
430
431         if (!strcasecmp("party_a", args.variable)) {
432                 option = AST_CDR_FLAG_PARTY_A;
433         } else if (!strcasecmp("disable", args.variable)) {
434                 option = AST_CDR_FLAG_DISABLE_ALL;
435         } else {
436                 ast_log(AST_LOG_WARNING, "Unknown option %s used with %s\n", args.variable, payload->cmd);
437                 return;
438         }
439
440         if (ast_true(payload->value)) {
441                 ast_cdr_set_property(ast_channel_name(payload->chan), option);
442         } else {
443                 ast_cdr_clear_property(ast_channel_name(payload->chan), option);
444         }
445 }
446
447
448 static int cdr_read(struct ast_channel *chan, const char *cmd, char *parse,
449                     char *buf, size_t len)
450 {
451         RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
452         RAII_VAR(struct cdr_func_payload *, payload, NULL, ao2_cleanup);
453         struct cdr_func_data output = { 0, };
454
455         if (!chan) {
456                 ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
457                 return -1;
458         }
459
460         if (!cdr_read_message_type()) {
461                 ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: message type not available\n",
462                         ast_channel_name(chan));
463                 return -1;
464         }
465
466         payload = ao2_alloc(sizeof(*payload), NULL);
467         if (!payload) {
468                 return -1;
469         }
470         payload->chan = chan;
471         payload->cmd = cmd;
472         payload->arguments = parse;
473         payload->data = &output;
474
475         buf[0] = '\0';/* Ensure the buffer is initialized. */
476         output.buf = buf;
477         output.len = len;
478
479         message = stasis_message_create(cdr_read_message_type(), payload);
480         if (!message) {
481                 ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: unable to create message\n",
482                         ast_channel_name(chan));
483                 return -1;
484         }
485
486         /* If this is a request on a dummy channel, we're doing post-processing on an
487          * already dispatched CDR. Simply call the callback to calculate the value and
488          * return, instead of posting to Stasis as we would for a running channel.
489          */
490         if (ast_strlen_zero(ast_channel_name(chan))) {
491                 cdr_read_callback(NULL, NULL, message);
492         } else {
493                 RAII_VAR(struct stasis_message_router *, router, ast_cdr_message_router(), ao2_cleanup);
494
495                 if (!router) {
496                         ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: no message router\n",
497                                 ast_channel_name(chan));
498                         return -1;
499                 }
500                 stasis_message_router_publish_sync(router, message);
501         }
502
503         return 0;
504 }
505
506 static int cdr_write(struct ast_channel *chan, const char *cmd, char *arguments,
507         const char *value)
508 {
509         struct stasis_message *message;
510         struct cdr_func_payload *payload;
511         struct stasis_message_router *router;
512         AST_DECLARE_APP_ARGS(args,
513                 AST_APP_ARG(variable);
514                 AST_APP_ARG(options);
515         );
516         char *parse;
517
518         if (!chan) {
519                 ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
520                 return -1;
521         }
522         if (ast_strlen_zero(arguments)) {
523                 ast_log(LOG_WARNING, "%s requires a variable (%s(variable)=value)\n)",
524                         cmd, cmd);
525                 return -1;
526         }
527         if (!value) {
528                 ast_log(LOG_WARNING, "%s requires a value (%s(variable)=value)\n)",
529                         cmd, cmd);
530                 return -1;
531         }
532
533         parse = ast_strdupa(arguments);
534         AST_STANDARD_APP_ARGS(args, parse);
535
536         /* These CDR variables are no longer supported or set directly on the channel */
537         if (!strcasecmp(args.variable, "accountcode")) {
538                 ast_log(LOG_WARNING, "Using the %s function to set 'accountcode' is deprecated. Please use the CHANNEL function instead.\n",
539                         cmd);
540                 ast_channel_lock(chan);
541                 ast_channel_accountcode_set(chan, value);
542                 ast_channel_unlock(chan);
543                 return 0;
544         }
545         if (!strcasecmp(args.variable, "amaflags")) {
546                 int amaflags;
547
548                 ast_log(LOG_WARNING, "Using the %s function to set 'amaflags' is deprecated. Please use the CHANNEL function instead.\n",
549                         cmd);
550                 if (isdigit(*value)) {
551                         if (sscanf(value, "%30d", &amaflags) != 1) {
552                                 amaflags = AST_AMA_NONE;
553                         }
554                 } else {
555                         amaflags = ast_channel_string2amaflag(value);
556                 }
557                 ast_channel_lock(chan);
558                 ast_channel_amaflags_set(chan, amaflags);
559                 ast_channel_unlock(chan);
560                 return 0;
561         }
562         if (!strcasecmp(args.variable, "peeraccount")) {
563                 ast_log(LOG_WARNING, "The 'peeraccount' setting is not supported. Please set the 'accountcode' on the appropriate channel using the CHANNEL function.\n");
564                 return 0;
565         }
566
567         /* The remaining CDR variables are handled by CDR processing code */
568         if (!cdr_write_message_type()) {
569                 ast_log(LOG_WARNING, "Failed to manipulate CDR for channel %s: message type not available\n",
570                         ast_channel_name(chan));
571                 return -1;
572         }
573
574         payload = ao2_alloc(sizeof(*payload), NULL);
575         if (!payload) {
576                 return -1;
577         }
578         payload->chan = chan;
579         payload->cmd = cmd;
580         payload->arguments = arguments;
581         payload->value = value;
582
583         message = stasis_message_create(cdr_write_message_type(), payload);
584         ao2_ref(payload, -1);
585         if (!message) {
586                 ast_log(LOG_WARNING, "Failed to manipulate CDR for channel %s: unable to create message\n",
587                         ast_channel_name(chan));
588                 return -1;
589         }
590         router = ast_cdr_message_router();
591         if (!router) {
592                 ast_log(LOG_WARNING, "Failed to manipulate CDR for channel %s: no message router\n",
593                         ast_channel_name(chan));
594                 ao2_ref(message, -1);
595                 return -1;
596         }
597         stasis_message_router_publish_sync(router, message);
598         ao2_ref(router, -1);
599         ao2_ref(message, -1);
600
601         return 0;
602 }
603
604 static int cdr_prop_write(struct ast_channel *chan, const char *cmd, char *parse,
605                      const char *value)
606 {
607         RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
608         RAII_VAR(struct cdr_func_payload *, payload, NULL, ao2_cleanup);
609         RAII_VAR(struct stasis_message_router *, router, ast_cdr_message_router(), ao2_cleanup);
610
611         if (!chan) {
612                 ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
613                 return -1;
614         }
615
616         if (!router) {
617                 ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: no message router\n",
618                         ast_channel_name(chan));
619                 return -1;
620         }
621
622         if (!cdr_prop_write_message_type()) {
623                 ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: message type not available\n",
624                         ast_channel_name(chan));
625                 return -1;
626         }
627
628         payload = ao2_alloc(sizeof(*payload), NULL);
629         if (!payload) {
630                 return -1;
631         }
632         payload->chan = chan;
633         payload->cmd = cmd;
634         payload->arguments = parse;
635         payload->value = value;
636
637         message = stasis_message_create(cdr_prop_write_message_type(), payload);
638         if (!message) {
639                 ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: unable to create message\n",
640                         ast_channel_name(chan));
641                 return -1;
642         }
643         stasis_message_router_publish_sync(router, message);
644
645         return 0;
646 }
647
648 static struct ast_custom_function cdr_function = {
649         .name = "CDR",
650         .read = cdr_read,
651         .write = cdr_write,
652 };
653
654 static struct ast_custom_function cdr_prop_function = {
655         .name = "CDR_PROP",
656         .read = NULL,
657         .write = cdr_prop_write,
658 };
659
660 static int unload_module(void)
661 {
662         RAII_VAR(struct stasis_message_router *, router, ast_cdr_message_router(), ao2_cleanup);
663         int res = 0;
664
665         if (router) {
666                 stasis_message_router_remove(router, cdr_prop_write_message_type());
667                 stasis_message_router_remove(router, cdr_write_message_type());
668                 stasis_message_router_remove(router, cdr_read_message_type());
669         }
670         STASIS_MESSAGE_TYPE_CLEANUP(cdr_read_message_type);
671         STASIS_MESSAGE_TYPE_CLEANUP(cdr_write_message_type);
672         STASIS_MESSAGE_TYPE_CLEANUP(cdr_prop_write_message_type);
673         res |= ast_custom_function_unregister(&cdr_function);
674         res |= ast_custom_function_unregister(&cdr_prop_function);
675
676         return res;
677 }
678
679 static int load_module(void)
680 {
681         RAII_VAR(struct stasis_message_router *, router, ast_cdr_message_router(), ao2_cleanup);
682         int res = 0;
683
684         if (!router) {
685                 return AST_MODULE_LOAD_DECLINE;
686         }
687
688         res |= STASIS_MESSAGE_TYPE_INIT(cdr_read_message_type);
689         res |= STASIS_MESSAGE_TYPE_INIT(cdr_write_message_type);
690         res |= STASIS_MESSAGE_TYPE_INIT(cdr_prop_write_message_type);
691         res |= ast_custom_function_register(&cdr_function);
692         res |= ast_custom_function_register(&cdr_prop_function);
693         res |= stasis_message_router_add(router, cdr_prop_write_message_type(),
694                                          cdr_prop_write_callback, NULL);
695         res |= stasis_message_router_add(router, cdr_write_message_type(),
696                                          cdr_write_callback, NULL);
697         res |= stasis_message_router_add(router, cdr_read_message_type(),
698                                          cdr_read_callback, NULL);
699
700         if (res) {
701                 unload_module();
702                 return AST_MODULE_LOAD_DECLINE;
703         }
704         return AST_MODULE_LOAD_SUCCESS;
705 }
706
707 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Call Detail Record (CDR) dialplan functions",
708         .support_level = AST_MODULE_SUPPORT_CORE,
709         .load = load_module,
710         .unload = unload_module,
711         .requires = "cdr",
712 );