res: Fix for Doxygen.
[asterisk/asterisk.git] / res / res_pjproject.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 Bridge PJPROJECT logging to Asterisk logging.
22  * \author David M. Lee, II <dlee@digium.com>
23  *
24  * PJPROJECT logging doesn't exactly match Asterisk logging, but mapping the two is
25  * not too bad. PJPROJECT log levels are identified by a single int. Limits are
26  * not specified by PJPROJECT, but their implementation used 1 through 6.
27  *
28  * The mapping is as follows:
29  *  - 0: LOG_ERROR
30  *  - 1: LOG_ERROR
31  *  - 2: LOG_WARNING
32  *  - 3: equivalent to ast_debug(level, ...) for res_pjproject.so
33  *  - 4: equivalent to ast_debug(level, ...) for res_pjproject.so
34  *  - 5: equivalent to ast_trace(level, ...) for res_pjproject.so
35  *  - 6: equivalent to ast_trace(level, ...) for res_pjproject.so
36  */
37
38 /*** MODULEINFO
39         <depend>pjproject</depend>
40         <depend>res_sorcery_config</depend>
41         <support_level>core</support_level>
42  ***/
43
44 /*** DOCUMENTATION
45         <configInfo name="res_pjproject" language="en_US">
46                 <synopsis>pjproject common configuration</synopsis>
47                 <configFile name="pjproject.conf">
48                         <configObject name="startup">
49                                 <synopsis>Asterisk startup time options for PJPROJECT</synopsis>
50                                 <description>
51                                         <note><para>The id of this object, as well as its type, must be
52                                         'startup' or it won't be found.</para></note>
53                                 </description>
54                                 <configOption name="type">
55                                         <synopsis>Must be of type 'startup'.</synopsis>
56                                 </configOption>
57                                 <configOption name="log_level" default="2">
58                                         <synopsis>Initial maximum pjproject logging level to log.</synopsis>
59                                         <description>
60                                                 <para>Valid values are: 0-6, and default</para>
61                                         <note><para>
62                                                 This option is needed very early in the startup process
63                                                 so it can only be read from config files because the
64                                                 modules for other methods have not been loaded yet.
65                                         </para></note>
66                                         </description>
67                                 </configOption>
68                         </configObject>
69                         <configObject name="log_mappings">
70                                 <synopsis>PJPROJECT to Asterisk Log Level Mapping</synopsis>
71                                 <description><para>Warnings and errors in the pjproject libraries are generally handled
72                                         by Asterisk.  In many cases, Asterisk wouldn't even consider them to
73                                         be warnings or errors so the messages emitted by pjproject directly
74                                         are either superfluous or misleading.  The 'log_mappings'
75                                         object allows mapping the pjproject levels to Asterisk levels, or nothing.
76                                         </para>
77                                         <note><para>The id of this object, as well as its type, must be
78                                         'log_mappings' or it won't be found.</para></note>
79                                 </description>
80                                 <configOption name="type">
81                                         <synopsis>Must be of type 'log_mappings'.</synopsis>
82                                 </configOption>
83                                 <configOption name="asterisk_error" default="0,1">
84                                         <synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_ERROR.</synopsis>
85                                 </configOption>
86                                 <configOption name="asterisk_warning" default="2">
87                                         <synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_WARNING.</synopsis>
88                                 </configOption>
89                                 <configOption name="asterisk_notice" default="">
90                                         <synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_NOTICE.</synopsis>
91                                 </configOption>
92                                 <configOption name="asterisk_verbose" default="">
93                                         <synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_VERBOSE.</synopsis>
94                                 </configOption>
95                                 <configOption name="asterisk_debug" default="3,4">
96                                         <synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_DEBUG.</synopsis>
97                                 </configOption>
98                                 <configOption name="asterisk_trace" default="5,6">
99                                         <synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_TRACE.</synopsis>
100                                 </configOption>
101                         </configObject>
102                 </configFile>
103         </configInfo>
104  ***/
105
106 #include "asterisk.h"
107
108 #include <stdarg.h>
109 #include <pjlib.h>
110 #include <pjsip.h>
111 #include <pj/log.h>
112
113 #include "asterisk/options.h"
114 #include "asterisk/logger.h"
115 #include "asterisk/module.h"
116 #include "asterisk/cli.h"
117 #include "asterisk/res_pjproject.h"
118 #include "asterisk/vector.h"
119 #include "asterisk/sorcery.h"
120 #include "asterisk/test.h"
121 #include "asterisk/netsock2.h"
122
123 static struct ast_sorcery *pjproject_sorcery;
124 static pj_log_func *log_cb_orig;
125 static unsigned decor_orig;
126
127 static AST_VECTOR(buildopts, char *) buildopts;
128
129 /*! Protection from other log intercept instances.  There can be only one at a time. */
130 AST_MUTEX_DEFINE_STATIC(pjproject_log_intercept_lock);
131
132 struct pjproject_log_intercept_data {
133         pthread_t thread;
134         int fd;
135 };
136
137 static struct pjproject_log_intercept_data pjproject_log_intercept = {
138         .thread = AST_PTHREADT_NULL,
139         .fd = -1,
140 };
141
142 struct log_mappings {
143         /*! Sorcery object details */
144         SORCERY_OBJECT(details);
145         /*! These are all comma-separated lists of pjproject log levels */
146         AST_DECLARE_STRING_FIELDS(
147                 /*! pjproject log levels mapped to Asterisk ERROR */
148                 AST_STRING_FIELD(asterisk_error);
149                 /*! pjproject log levels mapped to Asterisk WARNING */
150                 AST_STRING_FIELD(asterisk_warning);
151                 /*! pjproject log levels mapped to Asterisk NOTICE */
152                 AST_STRING_FIELD(asterisk_notice);
153                 /*! pjproject log levels mapped to Asterisk VERBOSE */
154                 AST_STRING_FIELD(asterisk_verbose);
155                 /*! pjproject log levels mapped to Asterisk DEBUG */
156                 AST_STRING_FIELD(asterisk_debug);
157                 /*! pjproject log levels mapped to Asterisk TRACE */
158                 AST_STRING_FIELD(asterisk_trace);
159         );
160 };
161
162 static struct log_mappings *default_log_mappings;
163
164 static struct log_mappings *get_log_mappings(void)
165 {
166         struct log_mappings *mappings;
167
168         mappings = ast_sorcery_retrieve_by_id(pjproject_sorcery, "log_mappings", "log_mappings");
169         if (!mappings) {
170                 return ao2_bump(default_log_mappings);
171         }
172
173         return mappings;
174 }
175
176 #define __LOG_SUPPRESS -1
177
178 static int get_log_level(int pj_level)
179 {
180         int mapped_level;
181         unsigned char l;
182         struct log_mappings *mappings;
183
184         mappings = get_log_mappings();
185         if (!mappings) {
186                 return __LOG_ERROR;
187         }
188
189         l = '0' + fmin(pj_level, 9);
190
191         if (strchr(mappings->asterisk_error, l)) {
192                 mapped_level = __LOG_ERROR;
193         } else if (strchr(mappings->asterisk_warning, l)) {
194                 mapped_level = __LOG_WARNING;
195         } else if (strchr(mappings->asterisk_notice, l)) {
196                 mapped_level = __LOG_NOTICE;
197         } else if (strchr(mappings->asterisk_verbose, l)) {
198                 mapped_level = __LOG_VERBOSE;
199         } else if (strchr(mappings->asterisk_debug, l)) {
200                 mapped_level = __LOG_DEBUG;
201         } else if (strchr(mappings->asterisk_trace, l)) {
202                 mapped_level = __LOG_TRACE;
203         } else {
204                 mapped_level = __LOG_SUPPRESS;
205         }
206
207         ao2_ref(mappings, -1);
208         return mapped_level;
209 }
210
211 static void log_forwarder(int level, const char *data, int len)
212 {
213         int ast_level;
214         /* PJPROJECT doesn't provide much in the way of source info */
215         const char * log_source = "pjproject";
216         int log_line = 0;
217         const char *log_func = "<?>";
218
219         if (pjproject_log_intercept.fd != -1
220                 && pjproject_log_intercept.thread == pthread_self()) {
221                 /*
222                  * We are handling a CLI command intercepting PJPROJECT
223                  * log output.
224                  */
225                 ast_cli(pjproject_log_intercept.fd, "%s\n", data);
226                 return;
227         }
228
229         ast_level = get_log_level(level);
230
231         if (ast_level == __LOG_SUPPRESS) {
232                 return;
233         }
234
235         /* PJPROJECT uses indention to indicate function call depth. We'll prepend
236          * log statements with a tab so they'll have a better shot at lining
237          * up */
238         ast_log(ast_level, log_source, log_line, log_func, "\t%s\n", data);
239 }
240
241 static void capture_buildopts_cb(int level, const char *data, int len)
242 {
243         char *dup;
244
245         if (strstr(data, "Teluu") || strstr(data, "Dumping")) {
246                 return;
247         }
248
249         dup = ast_strdup(ast_skip_blanks(data));
250         if (dup && AST_VECTOR_ADD_SORTED(&buildopts, dup, strcmp)) {
251                 ast_free(dup);
252         }
253 }
254
255 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
256 int ast_pjproject_get_buildopt(char *option, char *format_string, ...)
257 {
258         int res = 0;
259         char *format_temp;
260         int i;
261
262         format_temp = ast_alloca(strlen(option) + strlen(" : ") + strlen(format_string) + 1);
263         sprintf(format_temp, "%s : %s", option, format_string);
264
265         for (i = 0; i < AST_VECTOR_SIZE(&buildopts); i++) {
266                 va_list arg_ptr;
267                 va_start(arg_ptr, format_string);
268                 res = vsscanf(AST_VECTOR_GET(&buildopts, i), format_temp, arg_ptr);
269                 va_end(arg_ptr);
270                 if (res) {
271                         break;
272                 }
273         }
274
275         return res;
276 }
277 #pragma GCC diagnostic warning "-Wformat-nonliteral"
278
279 void ast_pjproject_log_intercept_begin(int fd)
280 {
281         /* Protect from other CLI instances trying to do this at the same time. */
282         ast_mutex_lock(&pjproject_log_intercept_lock);
283
284         pjproject_log_intercept.thread = pthread_self();
285         pjproject_log_intercept.fd = fd;
286 }
287
288 void ast_pjproject_log_intercept_end(void)
289 {
290         pjproject_log_intercept.fd = -1;
291         pjproject_log_intercept.thread = AST_PTHREADT_NULL;
292
293         ast_mutex_unlock(&pjproject_log_intercept_lock);
294 }
295
296 static char *handle_pjproject_show_buildopts(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
297 {
298         int i;
299
300         switch (cmd) {
301         case CLI_INIT:
302                 e->command = "pjproject show buildopts";
303                 e->usage =
304                         "Usage: pjproject show buildopts\n"
305                         "       Show the compile time config of the pjproject that Asterisk is\n"
306                         "       running against.\n";
307                 return NULL;
308         case CLI_GENERATE:
309                 return NULL;
310         }
311
312         ast_cli(a->fd, "PJPROJECT compile time config currently running against:\n");
313
314         for (i = 0; i < AST_VECTOR_SIZE(&buildopts); i++) {
315                 ast_cli(a->fd, "%s\n", AST_VECTOR_GET(&buildopts, i));
316         }
317
318         return CLI_SUCCESS;
319 }
320
321 static void mapping_destroy(void *object)
322 {
323         struct log_mappings *mappings = object;
324
325         ast_string_field_free_memory(mappings);
326 }
327
328 static void *mapping_alloc(const char *name)
329 {
330         struct log_mappings *mappings = ast_sorcery_generic_alloc(sizeof(*mappings), mapping_destroy);
331         if (!mappings) {
332                 return NULL;
333         }
334         ast_string_field_init(mappings, 128);
335
336         return mappings;
337 }
338
339 static char *handle_pjproject_show_log_mappings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
340 {
341         struct ast_variable *objset;
342         struct ast_variable *i;
343         struct log_mappings *mappings;
344
345         switch (cmd) {
346         case CLI_INIT:
347                 e->command = "pjproject show log mappings";
348                 e->usage =
349                         "Usage: pjproject show log mappings\n"
350                         "       Show pjproject to Asterisk log mappings\n";
351                 return NULL;
352         case CLI_GENERATE:
353                 return NULL;
354         }
355
356         ast_cli(a->fd, "PJPROJECT to Asterisk log mappings:\n");
357         ast_cli(a->fd, "Asterisk Level   : PJPROJECT log levels\n");
358
359         mappings = get_log_mappings();
360         if (!mappings) {
361                 ast_log(LOG_ERROR, "Unable to retrieve pjproject log_mappings\n");
362                 return CLI_SUCCESS;
363         }
364
365         objset = ast_sorcery_objectset_create(pjproject_sorcery, mappings);
366         if (!objset) {
367                 ao2_ref(mappings, -1);
368                 return CLI_SUCCESS;
369         }
370
371         for (i = objset; i; i = i->next) {
372                 ast_cli(a->fd, "%-16s : %s\n", i->name, i->value);
373         }
374         ast_variables_destroy(objset);
375
376         ao2_ref(mappings, -1);
377         return CLI_SUCCESS;
378 }
379
380 struct max_pjproject_log_level_check {
381         /*!
382          * Compile time sanity check to determine if
383          * MAX_PJ_LOG_MAX_LEVEL matches CLI syntax.
384          */
385         char check[1 / (6 == MAX_PJ_LOG_MAX_LEVEL)];
386 };
387
388 static char *handle_pjproject_set_log_level(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
389 {
390         int level_new;
391         int level_old;
392
393         switch (cmd) {
394         case CLI_INIT:
395                 e->command = "pjproject set log level {default|0|1|2|3|4|5|6}";
396                 e->usage =
397                         "Usage: pjproject set log level {default|<level>}\n"
398                         "\n"
399                         "       Set the maximum active pjproject logging level.\n"
400                         "       See pjproject.conf.sample for additional information\n"
401                         "       about the various levels pjproject uses.\n";
402                 return NULL;
403         case CLI_GENERATE:
404                 return NULL;
405         }
406
407         if (a->argc != 5) {
408                 return CLI_SHOWUSAGE;
409         }
410
411         if (!strcasecmp(a->argv[4], "default")) {
412                 level_new = DEFAULT_PJ_LOG_MAX_LEVEL;
413         } else {
414                 if (sscanf(a->argv[4], "%30d", &level_new) != 1
415                         || level_new < 0 || MAX_PJ_LOG_MAX_LEVEL < level_new) {
416                         return CLI_SHOWUSAGE;
417                 }
418         }
419
420         /* Update pjproject logging level */
421         if (ast_pjproject_max_log_level < level_new) {
422                 level_new = ast_pjproject_max_log_level;
423                 ast_cli(a->fd,
424                         "Asterisk built or linked with pjproject PJ_LOG_MAX_LEVEL=%d.\n"
425                         "Lowering request to the max supported level.\n",
426                         ast_pjproject_max_log_level);
427         }
428         level_old = ast_option_pjproject_log_level;
429         if (level_old == level_new) {
430                 ast_cli(a->fd, "pjproject log level is still %d.\n", level_old);
431         } else {
432                 ast_cli(a->fd, "pjproject log level was %d and is now %d.\n",
433                         level_old, level_new);
434                 ast_option_pjproject_log_level = level_new;
435                 pj_log_set_level(level_new);
436         }
437
438         return CLI_SUCCESS;
439 }
440
441 static char *handle_pjproject_show_log_level(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
442 {
443         switch (cmd) {
444         case CLI_INIT:
445                 e->command = "pjproject show log level";
446                 e->usage =
447                         "Usage: pjproject show log level\n"
448                         "\n"
449                         "       Show the current maximum active pjproject logging level.\n"
450                         "       See pjproject.conf.sample for additional information\n"
451                         "       about the various levels pjproject uses.\n";
452                 return NULL;
453         case CLI_GENERATE:
454                 return NULL;
455         }
456
457         if (a->argc != 4) {
458                 return CLI_SHOWUSAGE;
459         }
460
461         ast_cli(a->fd, "pjproject log level is %d.%s\n",
462                 ast_option_pjproject_log_level,
463                 ast_option_pjproject_log_level == DEFAULT_PJ_LOG_MAX_LEVEL ? " (default)" : "");
464
465         return CLI_SUCCESS;
466 }
467
468 static struct ast_cli_entry pjproject_cli[] = {
469         AST_CLI_DEFINE(handle_pjproject_set_log_level, "Set the maximum active pjproject logging level"),
470         AST_CLI_DEFINE(handle_pjproject_show_buildopts, "Show the compiled config of the pjproject in use"),
471         AST_CLI_DEFINE(handle_pjproject_show_log_mappings, "Show pjproject to Asterisk log mappings"),
472         AST_CLI_DEFINE(handle_pjproject_show_log_level, "Show the maximum active pjproject logging level"),
473 };
474
475 void ast_pjproject_caching_pool_init(pj_caching_pool *cp,
476         const pj_pool_factory_policy *policy, pj_size_t max_capacity)
477 {
478         /* Passing a max_capacity of zero disables caching pools */
479         pj_caching_pool_init(cp, policy, ast_option_pjproject_cache_pools ? max_capacity : 0);
480 }
481
482 void ast_pjproject_caching_pool_destroy(pj_caching_pool *cp)
483 {
484         pj_caching_pool_destroy(cp);
485 }
486
487 int ast_sockaddr_to_pj_sockaddr(const struct ast_sockaddr *addr, pj_sockaddr *pjaddr)
488 {
489         if (addr->ss.ss_family == AF_INET) {
490                 struct sockaddr_in *sin = (struct sockaddr_in *) &addr->ss;
491                 pjaddr->ipv4.sin_family = pj_AF_INET();
492 #ifdef HAVE_PJPROJECT_BUNDLED
493                 pjaddr->ipv4.sin_addr = sin->sin_addr;
494 #else
495                 pjaddr->ipv4.sin_addr.s_addr = sin->sin_addr.s_addr;
496 #endif
497                 pjaddr->ipv4.sin_port   = sin->sin_port;
498         } else if (addr->ss.ss_family == AF_INET6) {
499                 struct sockaddr_in6 *sin = (struct sockaddr_in6 *) &addr->ss;
500                 pjaddr->ipv6.sin6_family   = pj_AF_INET6();
501                 pjaddr->ipv6.sin6_port     = sin->sin6_port;
502                 pjaddr->ipv6.sin6_flowinfo = sin->sin6_flowinfo;
503                 pjaddr->ipv6.sin6_scope_id = sin->sin6_scope_id;
504                 memcpy(&pjaddr->ipv6.sin6_addr, &sin->sin6_addr, sizeof(pjaddr->ipv6.sin6_addr));
505         } else {
506                 memset(pjaddr, 0, sizeof(*pjaddr));
507                 return -1;
508         }
509         return 0;
510 }
511
512 int ast_sockaddr_from_pj_sockaddr(struct ast_sockaddr *addr, const pj_sockaddr *pjaddr)
513 {
514         if (pjaddr->addr.sa_family == pj_AF_INET()) {
515                 struct sockaddr_in *sin = (struct sockaddr_in *) &addr->ss;
516                 sin->sin_family = AF_INET;
517 #ifdef HAVE_PJPROJECT_BUNDLED
518                 sin->sin_addr = pjaddr->ipv4.sin_addr;
519 #else
520                 sin->sin_addr.s_addr = pjaddr->ipv4.sin_addr.s_addr;
521 #endif
522                 sin->sin_port   = pjaddr->ipv4.sin_port;
523                 addr->len = sizeof(struct sockaddr_in);
524         } else if (pjaddr->addr.sa_family == pj_AF_INET6()) {
525                 struct sockaddr_in6 *sin = (struct sockaddr_in6 *) &addr->ss;
526                 sin->sin6_family   = AF_INET6;
527                 sin->sin6_port     = pjaddr->ipv6.sin6_port;
528                 sin->sin6_flowinfo = pjaddr->ipv6.sin6_flowinfo;
529                 sin->sin6_scope_id = pjaddr->ipv6.sin6_scope_id;
530                 memcpy(&sin->sin6_addr, &pjaddr->ipv6.sin6_addr, sizeof(sin->sin6_addr));
531                 addr->len = sizeof(struct sockaddr_in6);
532         } else {
533                 memset(addr, 0, sizeof(*addr));
534                 return -1;
535         }
536         return 0;
537 }
538
539 #ifdef TEST_FRAMEWORK
540 static void fill_with_garbage(void *x, ssize_t len)
541 {
542         unsigned char *w = x;
543         while (len > 0) {
544                 int r = ast_random();
545                 memcpy(w, &r, len > sizeof(r) ? sizeof(r) : len);
546                 w += sizeof(r);
547                 len -= sizeof(r);
548         }
549 }
550
551 AST_TEST_DEFINE(ast_sockaddr_to_pj_sockaddr_test)
552 {
553         char *candidates[] = {
554                 "127.0.0.1:5555",
555                 "[::]:4444",
556                 "192.168.0.100:0",
557                 "[fec0::1:80]:0",
558                 "[fec0::1]:80",
559                 NULL,
560         }, **candidate = candidates;
561
562         switch (cmd) {
563         case TEST_INIT:
564                 info->name = "ast_sockaddr_to_pj_sockaddr_test";
565                 info->category = "/res/res_pjproject/";
566                 info->summary = "Validate conversions from an ast_sockaddr to a pj_sockaddr";
567                 info->description = "This test converts an ast_sockaddr to a pj_sockaddr and validates\n"
568                         "that the two evaluate to the same string when formatted.";
569                 return AST_TEST_NOT_RUN;
570         case TEST_EXECUTE:
571                 break;
572         }
573
574         while (*candidate) {
575                 struct ast_sockaddr addr = {{0,}};
576                 pj_sockaddr pjaddr;
577                 char buffer[512];
578
579                 fill_with_garbage(&pjaddr, sizeof(pj_sockaddr));
580
581                 if (!ast_sockaddr_parse(&addr, *candidate, 0)) {
582                         ast_test_status_update(test, "Failed to parse candidate IP: %s\n", *candidate);
583                         return AST_TEST_FAIL;
584                 }
585
586                 if (ast_sockaddr_to_pj_sockaddr(&addr, &pjaddr)) {
587                         ast_test_status_update(test, "Failed to convert ast_sockaddr to pj_sockaddr: %s\n", *candidate);
588                         return AST_TEST_FAIL;
589                 }
590
591                 pj_sockaddr_print(&pjaddr, buffer, sizeof(buffer), 1 | 2);
592
593                 if (strcmp(*candidate, buffer)) {
594                         ast_test_status_update(test, "Converted sockaddrs do not match: \"%s\" and \"%s\"\n",
595                                 *candidate,
596                                 buffer);
597                         return AST_TEST_FAIL;
598                 }
599
600                 candidate++;
601         }
602
603         return AST_TEST_PASS;
604 }
605
606 AST_TEST_DEFINE(ast_sockaddr_from_pj_sockaddr_test)
607 {
608         char *candidates[] = {
609                 "127.0.0.1:5555",
610                 "[::]:4444",
611                 "192.168.0.100:0",
612                 "[fec0::1:80]:0",
613                 "[fec0::1]:80",
614                 NULL,
615         }, **candidate = candidates;
616
617         switch (cmd) {
618         case TEST_INIT:
619                 info->name = "ast_sockaddr_from_pj_sockaddr_test";
620                 info->category = "/res/res_pjproject/";
621                 info->summary = "Validate conversions from a pj_sockaddr to an ast_sockaddr";
622                 info->description = "This test converts a pj_sockaddr to an ast_sockaddr and validates\n"
623                         "that the two evaluate to the same string when formatted.";
624                 return AST_TEST_NOT_RUN;
625         case TEST_EXECUTE:
626                 break;
627         }
628
629         while (*candidate) {
630                 struct ast_sockaddr addr = {{0,}};
631                 pj_sockaddr pjaddr;
632                 pj_str_t t;
633                 char buffer[512];
634
635                 fill_with_garbage(&addr, sizeof(addr));
636
637                 pj_strset(&t, *candidate, strlen(*candidate));
638
639                 if (pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &t, &pjaddr) != PJ_SUCCESS) {
640                         ast_test_status_update(test, "Failed to parse candidate IP: %s\n", *candidate);
641                         return AST_TEST_FAIL;
642                 }
643
644                 if (ast_sockaddr_from_pj_sockaddr(&addr, &pjaddr)) {
645                         ast_test_status_update(test, "Failed to convert pj_sockaddr to ast_sockaddr: %s\n", *candidate);
646                         return AST_TEST_FAIL;
647                 }
648
649                 snprintf(buffer, sizeof(buffer), "%s", ast_sockaddr_stringify(&addr));
650
651                 if (strcmp(*candidate, buffer)) {
652                         ast_test_status_update(test, "Converted sockaddrs do not match: \"%s\" and \"%s\"\n",
653                                 *candidate,
654                                 buffer);
655                         return AST_TEST_FAIL;
656                 }
657
658                 candidate++;
659         }
660
661         return AST_TEST_PASS;
662 }
663 #endif
664
665 static int load_module(void)
666 {
667         ast_debug(3, "Starting PJPROJECT logging to Asterisk logger\n");
668
669         if (!(pjproject_sorcery = ast_sorcery_open())) {
670                 ast_log(LOG_ERROR, "Failed to open SIP sorcery failed to open\n");
671                 return AST_MODULE_LOAD_DECLINE;
672         }
673
674         ast_sorcery_apply_default(pjproject_sorcery, "log_mappings", "config", "pjproject.conf,criteria=type=log_mappings");
675         if (ast_sorcery_object_register(pjproject_sorcery, "log_mappings", mapping_alloc, NULL, NULL)) {
676                 ast_log(LOG_WARNING, "Failed to register pjproject log_mappings object with sorcery\n");
677                 ast_sorcery_unref(pjproject_sorcery);
678                 pjproject_sorcery = NULL;
679                 return AST_MODULE_LOAD_DECLINE;
680         }
681
682         ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "type", "", OPT_NOOP_T, 0, 0);
683         ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_debug", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_debug));
684         ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_error", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_error));
685         ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_warning", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_warning));
686         ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_notice", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_notice));
687         ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_verbose", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_verbose));
688         ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_trace", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_trace));
689
690         default_log_mappings = ast_sorcery_alloc(pjproject_sorcery, "log_mappings", "log_mappings");
691         if (!default_log_mappings) {
692                 ast_log(LOG_ERROR, "Unable to allocate memory for pjproject log_mappings\n");
693                 return AST_MODULE_LOAD_DECLINE;
694         }
695         ast_string_field_set(default_log_mappings, asterisk_error, "0,1");
696         ast_string_field_set(default_log_mappings, asterisk_warning, "2");
697         ast_string_field_set(default_log_mappings, asterisk_debug, "3,4");
698         ast_string_field_set(default_log_mappings, asterisk_trace, "5,6");
699
700         ast_sorcery_load(pjproject_sorcery);
701
702         AST_PJPROJECT_INIT_LOG_LEVEL();
703         pj_init();
704
705         decor_orig = pj_log_get_decor();
706         log_cb_orig = pj_log_get_log_func();
707
708         if (AST_VECTOR_INIT(&buildopts, 64)) {
709                 return AST_MODULE_LOAD_DECLINE;
710         }
711
712         /*
713          * On startup, we want to capture the dump once and store it.
714          */
715         pj_log_set_log_func(capture_buildopts_cb);
716         pj_log_set_decor(0);
717         pj_log_set_level(MAX_PJ_LOG_MAX_LEVEL);/* Set level to guarantee the dump output. */
718         pj_dump_config();
719         pj_log_set_decor(PJ_LOG_HAS_SENDER | PJ_LOG_HAS_INDENT);
720         pj_log_set_log_func(log_forwarder);
721         if (ast_pjproject_max_log_level < ast_option_pjproject_log_level) {
722                 ast_log(LOG_WARNING,
723                         "Asterisk built or linked with pjproject PJ_LOG_MAX_LEVEL=%d which is too low for startup level: %d.\n",
724                         ast_pjproject_max_log_level, ast_option_pjproject_log_level);
725                 ast_option_pjproject_log_level = ast_pjproject_max_log_level;
726         }
727         pj_log_set_level(ast_option_pjproject_log_level);
728         if (!AST_VECTOR_SIZE(&buildopts)) {
729                 ast_log(LOG_NOTICE,
730                         "Asterisk built or linked with pjproject PJ_LOG_MAX_LEVEL=%d which is too low to get buildopts.\n",
731                         ast_pjproject_max_log_level);
732         }
733
734         ast_cli_register_multiple(pjproject_cli, ARRAY_LEN(pjproject_cli));
735
736         AST_TEST_REGISTER(ast_sockaddr_to_pj_sockaddr_test);
737         AST_TEST_REGISTER(ast_sockaddr_from_pj_sockaddr_test);
738
739         return AST_MODULE_LOAD_SUCCESS;
740 }
741
742 #define NOT_EQUALS(a, b) (a != b)
743
744 static int unload_module(void)
745 {
746         ast_cli_unregister_multiple(pjproject_cli, ARRAY_LEN(pjproject_cli));
747         pj_log_set_log_func(log_cb_orig);
748         pj_log_set_decor(decor_orig);
749
750         AST_VECTOR_CALLBACK_VOID(&buildopts, ast_free);
751         AST_VECTOR_FREE(&buildopts);
752
753         ast_debug(3, "Stopped PJPROJECT logging to Asterisk logger\n");
754
755         pj_shutdown();
756
757         ao2_cleanup(default_log_mappings);
758         default_log_mappings = NULL;
759
760         ast_sorcery_unref(pjproject_sorcery);
761
762         AST_TEST_UNREGISTER(ast_sockaddr_to_pj_sockaddr_test);
763         AST_TEST_UNREGISTER(ast_sockaddr_from_pj_sockaddr_test);
764
765         return 0;
766 }
767
768 static int reload_module(void)
769 {
770         if (pjproject_sorcery) {
771                 ast_sorcery_reload(pjproject_sorcery);
772         }
773
774         return AST_MODULE_LOAD_SUCCESS;
775 }
776
777 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "PJPROJECT Log and Utility Support",
778         .support_level = AST_MODULE_SUPPORT_CORE,
779         .load = load_module,
780         .unload = unload_module,
781         .reload = reload_module,
782         .load_pri = AST_MODPRI_CHANNEL_DEPEND - 6,
783         .requires = "res_sorcery_config",
784 );