install_prereq: Add SUSE.
[asterisk/asterisk.git] / res / res_sdp_translator_pjmedia.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2017, Digium, Inc.
5  *
6  * Mark Michelson <mmichelson@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 #include "asterisk.h"
20
21 #include <pjlib.h>
22 #include <pjmedia.h>
23
24 #include "asterisk/res_pjproject.h"
25 #include "asterisk/sdp_translator.h"
26 #include "asterisk/sdp_options.h"
27 #include "asterisk/vector.h"
28 #include "asterisk/netsock2.h"
29 #include "asterisk/utils.h"
30 #include "asterisk/config.h"
31 #include "asterisk/test.h"
32 #include "asterisk/module.h"
33
34 #include "asterisk/sdp.h"
35
36 /*** MODULEINFO
37         <depend>pjproject</depend>
38         <support_level>core</support_level>
39  ***/
40
41 /*
42  * XXX TODO: The memory in the pool is held onto longer than necessary.  It
43  * is kept and grows for the duration of the associated chan_pjsip session.
44  *
45  * The translation API does not need to be so generic.  The users will know
46  * at compile time what the non-Asterisk SDP format they have or need.  They
47  * should simply call the specific translation functions.  However, to make
48  * this a loadable module we need to be able to keep it in memory when a
49  * dependent module is loaded.
50  *
51  * To address both issues I propose this API:
52  *
53  * void ast_sdp_translate_pjmedia_ref(void) - Inc this module's user ref
54  * void ast_sdp_translate_pjmedia_unref(void) - Dec this module's user ref.
55  *    The res_pjsip_session.c:ast_sip_session_alloc() can call the module ref
56  *    and the session's destructor can call the module unref.
57  *
58  * struct ast_sdp *ast_sdp_translate_pjmedia_from(const pjmedia_sdp_session *pjmedia_sdp);
59  *
60  * pjmedia_sdp_session *ast_sdp_translate_pjmedia_to(const struct ast_sdp *sdp, pj_pool_t *pool);
61  *    Passing in a memory pool allows the memory to be obtained from an
62  *    rdata memory pool that will be released when the message processing
63  *    is complete.  This prevents memory from accumulating for the duration
64  *    of a call.
65  *
66  * int ast_sdp_translate_pjmedia_set_remote_sdp(struct ast_sdp_state *sdp_state, const pjmedia_sdp_session *remote);
67  * const pjmedia_sdp_session *ast_sdp_translate_pjmedia_get_local_sdp(struct ast_sdp_state *sdp_state, pj_pool_t *pool);
68  *    These two functions just do the bookkeeping to translate and set or get
69  *    the requested SDP.
70  *
71  *
72  * XXX TODO: This code doesn't handle allocation failures very well.  i.e.,
73  *   It assumes they will never happen.
74  *
75  * XXX TODO: This code uses ast_alloca() inside loops.  Doing so if the number
76  *   of times through the loop is unconstrained will blow the stack.
77  *   See dupa_pj_str() usage.
78  */
79
80 static pj_caching_pool sdp_caching_pool;
81
82
83 static void *pjmedia_new(void)
84 {
85         pj_pool_t *pool;
86
87         pool = pj_pool_create(&sdp_caching_pool.factory, "pjmedia sdp translator", 1024, 1024, NULL);
88
89         return pool;
90 }
91
92 static void pjmedia_free(void *translator_priv)
93 {
94         pj_pool_t *pool = translator_priv;
95
96         pj_pool_release(pool);
97 }
98
99 #define dupa_pj_str(pjstr) \
100 ({ \
101         char *dest = ast_alloca(pjstr.slen + 1); \
102         memcpy(dest, pjstr.ptr, pjstr.slen); \
103         dest[pjstr.slen] = '\0'; \
104         dest; \
105 })
106
107 static struct ast_sdp_m_line *pjmedia_copy_m_line(struct pjmedia_sdp_media *pjmedia_m_line)
108 {
109         int i;
110
111         struct ast_sdp_c_line *c_line = pjmedia_m_line->conn ?
112                 ast_sdp_c_alloc(dupa_pj_str(pjmedia_m_line->conn->addr_type),
113                 dupa_pj_str(pjmedia_m_line->conn->addr)) : NULL;
114
115         struct ast_sdp_m_line *m_line = ast_sdp_m_alloc(dupa_pj_str(pjmedia_m_line->desc.media),
116                 pjmedia_m_line->desc.port, pjmedia_m_line->desc.port_count,
117                 dupa_pj_str(pjmedia_m_line->desc.transport), c_line);
118
119         for (i = 0; i < pjmedia_m_line->desc.fmt_count; ++i) {
120                 ast_sdp_m_add_payload(m_line,
121                         ast_sdp_payload_alloc(dupa_pj_str(pjmedia_m_line->desc.fmt[i])));
122         }
123
124         for (i = 0; i < pjmedia_m_line->attr_count; ++i) {
125                 ast_sdp_m_add_a(m_line, ast_sdp_a_alloc(dupa_pj_str(pjmedia_m_line->attr[i]->name),
126                         dupa_pj_str(pjmedia_m_line->attr[i]->value)));
127         }
128
129         return m_line;
130 }
131
132 static void pjmedia_copy_a_lines(struct ast_sdp *new_sdp, const pjmedia_sdp_session *pjmedia_sdp)
133 {
134         int i;
135
136         for (i = 0; i < pjmedia_sdp->attr_count; ++i) {
137                 ast_sdp_add_a(new_sdp, ast_sdp_a_alloc(dupa_pj_str(pjmedia_sdp->attr[i]->name),
138                         dupa_pj_str(pjmedia_sdp->attr[i]->value)));
139         }
140 }
141
142 static void pjmedia_copy_m_lines(struct ast_sdp *new_sdp,
143         const struct pjmedia_sdp_session *pjmedia_sdp)
144 {
145         int i;
146
147         for (i = 0; i < pjmedia_sdp->media_count; ++i) {
148                 ast_sdp_add_m(new_sdp, pjmedia_copy_m_line(pjmedia_sdp->media[i]));
149         }
150 }
151
152 static struct ast_sdp *pjmedia_to_sdp(const void *in, void *translator_priv)
153 {
154         const struct pjmedia_sdp_session *pjmedia_sdp = in;
155
156         struct ast_sdp_o_line *o_line = ast_sdp_o_alloc(dupa_pj_str(pjmedia_sdp->origin.user),
157                 pjmedia_sdp->origin.id, pjmedia_sdp->origin.version,
158                 dupa_pj_str(pjmedia_sdp->origin.addr_type), dupa_pj_str(pjmedia_sdp->origin.addr));
159
160         struct ast_sdp_c_line *c_line = pjmedia_sdp->conn ?
161                 ast_sdp_c_alloc(dupa_pj_str(pjmedia_sdp->conn->addr_type),
162                         dupa_pj_str(pjmedia_sdp->conn->addr)) : NULL;
163
164         struct ast_sdp_s_line *s_line = ast_sdp_s_alloc(dupa_pj_str(pjmedia_sdp->name));
165
166         struct ast_sdp_t_line *t_line = ast_sdp_t_alloc(pjmedia_sdp->time.start,
167                 pjmedia_sdp->time.stop);
168
169         struct ast_sdp *new_sdp = ast_sdp_alloc(o_line, c_line, s_line, t_line);
170
171         pjmedia_copy_a_lines(new_sdp, pjmedia_sdp);
172         pjmedia_copy_m_lines(new_sdp, pjmedia_sdp);
173
174         return new_sdp;
175 }
176
177 static void copy_o_line_pjmedia(pj_pool_t *pool, pjmedia_sdp_session *pjmedia_sdp,
178         struct ast_sdp_o_line *o_line)
179 {
180         pjmedia_sdp->origin.id = o_line->session_id;
181         pjmedia_sdp->origin.version = o_line->session_version;
182         pj_strdup2(pool, &pjmedia_sdp->origin.user, o_line->username);
183         pj_strdup2(pool, &pjmedia_sdp->origin.addr_type, o_line->address_type);
184         pj_strdup2(pool, &pjmedia_sdp->origin.addr, o_line->address);
185         pj_strdup2(pool, &pjmedia_sdp->origin.net_type, "IN");
186 }
187
188 static void copy_s_line_pjmedia(pj_pool_t *pool, pjmedia_sdp_session *pjmedia_sdp,
189         struct ast_sdp_s_line *s_line)
190 {
191         pj_strdup2(pool, &pjmedia_sdp->name, s_line->session_name);
192 }
193
194 static void copy_t_line_pjmedia(pj_pool_t *pool, pjmedia_sdp_session *pjmedia_sdp,
195         struct ast_sdp_t_line *t_line)
196 {
197         pjmedia_sdp->time.start = t_line->start_time;
198         pjmedia_sdp->time.stop = t_line->stop_time;
199 }
200
201 static void copy_c_line_pjmedia(pj_pool_t *pool, pjmedia_sdp_conn **conn,
202         struct ast_sdp_c_line *c_line)
203 {
204         pjmedia_sdp_conn *local_conn;
205         local_conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
206         pj_strdup2(pool, &local_conn->addr_type, c_line->address_type);
207         pj_strdup2(pool, &local_conn->addr, c_line->address);
208         pj_strdup2(pool, &local_conn->net_type, "IN");
209
210         *conn = local_conn;
211 }
212
213 static void copy_a_lines_pjmedia(pj_pool_t *pool, pjmedia_sdp_session *pjmedia_sdp,
214         const struct ast_sdp *sdp)
215 {
216         int i;
217
218         for (i = 0; i < ast_sdp_get_a_count(sdp); ++i) {
219                 pjmedia_sdp_attr *attr;
220                 pj_str_t value;
221                 struct ast_sdp_a_line *a_line;
222
223                 a_line = ast_sdp_get_a(sdp, i);
224                 pj_strdup2(pool, &value, a_line->value);
225                 attr = pjmedia_sdp_attr_create(pool, a_line->name, &value);
226                 pjmedia_sdp_session_add_attr(pjmedia_sdp, attr);
227         }
228 }
229
230 static void copy_a_lines_pjmedia_media(pj_pool_t *pool, pjmedia_sdp_media *media,
231         struct ast_sdp_m_line *m_line)
232 {
233         int i;
234
235         for (i = 0; i < ast_sdp_m_get_a_count(m_line); ++i) {
236                 pjmedia_sdp_attr *attr;
237                 pj_str_t value;
238                 struct ast_sdp_a_line *a_line;
239
240                 a_line = ast_sdp_m_get_a(m_line, i);
241                 pj_strdup2(pool, &value, a_line->value);
242                 attr = pjmedia_sdp_attr_create(pool, a_line->name, &value);
243                 pjmedia_sdp_media_add_attr(media, attr);
244         }
245 }
246
247 static void copy_m_line_pjmedia(pj_pool_t *pool, pjmedia_sdp_media *media,
248         struct ast_sdp_m_line *m_line)
249 {
250         int i;
251
252         media->desc.port = m_line->port;
253         media->desc.port_count = m_line->port_count;
254         pj_strdup2(pool, &media->desc.transport, m_line->proto);
255         pj_strdup2(pool, &media->desc.media, m_line->type);
256
257         for (i = 0; i < ast_sdp_m_get_payload_count(m_line); ++i) {
258                 pj_strdup2(pool, &media->desc.fmt[i], ast_sdp_m_get_payload(m_line, i)->fmt);
259                 ++media->desc.fmt_count;
260         }
261         if (m_line->c_line && m_line->c_line->address) {
262                 copy_c_line_pjmedia(pool, &media->conn, m_line->c_line);
263         }
264         copy_a_lines_pjmedia_media(pool, media, m_line);
265 }
266
267 static void copy_m_lines_pjmedia(pj_pool_t *pool, pjmedia_sdp_session *pjmedia_sdp,
268         const struct ast_sdp *sdp)
269 {
270         int i;
271
272         for (i = 0; i < ast_sdp_get_m_count(sdp); ++i) {
273                 pjmedia_sdp_media *media;
274
275                 media = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
276                 copy_m_line_pjmedia(pool, media, ast_sdp_get_m(sdp, i));
277                 pjmedia_sdp->media[pjmedia_sdp->media_count] = media;
278                 ++pjmedia_sdp->media_count;
279         }
280 }
281
282 static const void *sdp_to_pjmedia(const struct ast_sdp *sdp, void *translator_priv)
283 {
284         pj_pool_t *pool = translator_priv;
285         pjmedia_sdp_session *pjmedia_sdp;
286
287         pjmedia_sdp = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session);
288         copy_o_line_pjmedia(pool, pjmedia_sdp, sdp->o_line);
289         copy_s_line_pjmedia(pool, pjmedia_sdp, sdp->s_line);
290         copy_t_line_pjmedia(pool, pjmedia_sdp, sdp->t_line);
291         copy_c_line_pjmedia(pool, &pjmedia_sdp->conn, sdp->c_line);
292         copy_a_lines_pjmedia(pool, pjmedia_sdp, sdp);
293         copy_m_lines_pjmedia(pool, pjmedia_sdp, sdp);
294         return pjmedia_sdp;
295 }
296
297 static struct ast_sdp_translator_ops pjmedia_translator = {
298         .repr = AST_SDP_IMPL_PJMEDIA,
299         .translator_new = pjmedia_new,
300         .translator_free = pjmedia_free,
301         .to_sdp = pjmedia_to_sdp,
302         .from_sdp = sdp_to_pjmedia,
303 };
304
305 #ifdef TEST_FRAMEWORK
306
307 static int verify_s_line(struct ast_sdp_s_line *s_line, char *expected)
308 {
309         return strcmp(s_line->session_name, expected) == 0;
310 }
311
312 static int verify_c_line(struct ast_sdp_c_line *c_line, char *family, char *addr)
313 {
314         return strcmp(c_line->address_type, family) == 0 && strcmp(c_line->address, addr) == 0;
315 }
316
317 static int verify_t_line(struct ast_sdp_t_line *t_line, uint32_t start, uint32_t end)
318 {
319         return t_line->start_time == start && t_line->stop_time == end;
320 }
321
322 static int verify_m_line(struct ast_sdp *sdp, int index, char *type, int port,
323         int port_count, char *profile, ...)
324 {
325         struct ast_sdp_m_line *m_line;
326         int res;
327         va_list ap;
328         int i;
329
330         m_line = ast_sdp_get_m(sdp, index);
331
332         res = strcmp(m_line->type, type) == 0;
333         res |= m_line->port == port;
334         res |= m_line->port_count == port_count;
335         res |= strcmp(m_line->proto, profile) == 0;
336
337         va_start(ap, profile);
338         for (i = 0; i < ast_sdp_m_get_payload_count(m_line); ++i) {
339                 char *payload;
340
341                 payload = va_arg(ap, char *);
342                 if (!payload) {
343                         res = -1;
344                         break;
345                 }
346                 res |= strcmp(ast_sdp_m_get_payload(m_line, i)->fmt, payload) == 0;
347         }
348         va_end(ap);
349         return res;
350 }
351
352 static int verify_a_line(struct ast_sdp *sdp, int m_index, int a_index, char *name,
353         char *value)
354 {
355         struct ast_sdp_m_line *m_line;
356         struct ast_sdp_a_line *a_line;
357
358         m_line = ast_sdp_get_m(sdp, m_index);
359         a_line = ast_sdp_m_get_a(m_line, a_index);
360
361         return strcmp(a_line->name, name) == 0 && strcmp(a_line->value, value) == 0;
362 }
363
364 AST_TEST_DEFINE(pjmedia_to_sdp_test)
365 {
366         struct ast_sdp_translator *translator;
367         pj_pool_t *pool;
368         char *sdp_str =
369       "v=0\r\n"
370       "o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\r\n"
371       "s= \r\n"
372       "c=IN IP4 host.atlanta.example.com\r\n"
373       "t=123 456\r\n"
374       "m=audio 49170 RTP/AVP 0 8 97\r\n"
375       "a=rtpmap:0 PCMU/8000\r\n"
376       "a=rtpmap:8 PCMA/8000\r\n"
377       "a=rtpmap:97 iLBC/8000\r\n"
378           "a=sendrecv\r\n"
379       "m=video 51372 RTP/AVP 31 32\r\n"
380       "a=rtpmap:31 H261/90000\r\n"
381       "a=rtpmap:32 MPV/90000\r\n";
382         pjmedia_sdp_session *pjmedia_sdp;
383         struct ast_sdp *sdp = NULL;
384         pj_status_t status;
385         enum ast_test_result_state res = AST_TEST_PASS;
386
387         switch (cmd) {
388         case TEST_INIT:
389                 info->name = "pjmedia_to_sdp";
390                 info->category = "/main/sdp/";
391                 info->summary = "PJMEDIA to SDP unit test";
392                 info->description =
393                         "Ensures PJMEDIA SDPs are translated correctly";
394                 return AST_TEST_NOT_RUN;
395         case TEST_EXECUTE:
396                 break;
397         }
398
399         pool = pj_pool_create(&sdp_caching_pool.factory, "pjmedia to sdp test", 1024, 1024, NULL);
400
401         translator = ast_sdp_translator_new(AST_SDP_IMPL_PJMEDIA);
402         if (!translator) {
403                 ast_test_status_update(test, "Failed to create SDP translator\n");
404                 res = AST_TEST_FAIL;
405                 goto cleanup;
406         }
407
408         status = pjmedia_sdp_parse(pool, sdp_str, strlen(sdp_str), &pjmedia_sdp);
409         if (status != PJ_SUCCESS) {
410                 ast_test_status_update(test, "Error parsing SDP\n");
411                 res = AST_TEST_FAIL;
412                 goto cleanup;
413         }
414
415         sdp = ast_sdp_translator_to_sdp(translator, pjmedia_sdp);
416
417         if (strcmp(sdp->o_line->username, "alice")) {
418                 ast_test_status_update(test, "Unexpected SDP user '%s'\n", sdp->o_line->username);
419                 res = AST_TEST_FAIL;
420                 goto cleanup;
421         } else if (sdp->o_line->session_id != 2890844526UL) {
422                 ast_test_status_update(test, "Unexpected SDP id '%" PRId64 "lu'\n", sdp->o_line->session_id);
423                 res = AST_TEST_FAIL;
424                 goto cleanup;
425         } else if (sdp->o_line->session_version != 2890844527UL) {
426                 ast_test_status_update(test, "Unexpected SDP version '%" PRId64 "'\n", sdp->o_line->session_version);
427                 res = AST_TEST_FAIL;
428                 goto cleanup;
429         } else if (strcmp(sdp->o_line->address_type, "IP4")) {
430                 ast_test_status_update(test, "Unexpected address family '%s'\n", sdp->o_line->address_type);
431                 res = AST_TEST_FAIL;
432                 goto cleanup;
433         } else if (strcmp(sdp->o_line->address, "host.atlanta.example.com")) {
434                 ast_test_status_update(test, "Unexpected address '%s'\n", sdp->o_line->address);
435                 res = AST_TEST_FAIL;
436                 goto cleanup;
437         }
438
439         if (!verify_s_line(sdp->s_line, " ")) {
440                 ast_test_status_update(test, "Bad s line\n");
441                 res = AST_TEST_FAIL;
442                 goto cleanup;
443         } else if (!verify_c_line(sdp->c_line, "IP4", "host.atlanta.example.com")) {
444                 ast_test_status_update(test, "Bad c line\n");
445                 res = AST_TEST_FAIL;
446                 goto cleanup;
447         } else if (!verify_t_line(sdp->t_line, 123, 456)) {
448                 ast_test_status_update(test, "Bad t line\n");
449                 res = AST_TEST_FAIL;
450                 goto cleanup;
451         }
452
453         if (!verify_m_line(sdp, 0, "audio", 49170, 1, "RTP/AVP", "0", "8", "97", NULL)) {
454                 ast_test_status_update(test, "Bad m line 1\n");
455                 res = AST_TEST_FAIL;
456                 goto cleanup;
457         } else if (!verify_a_line(sdp, 0, 0, "rtpmap", "0 PCMU/8000")) {
458                 ast_test_status_update(test, "Bad a line 1\n");
459                 res = AST_TEST_FAIL;
460                 goto cleanup;
461         } else if (!verify_a_line(sdp, 0, 1, "rtpmap", "8 PCMA/8000")) {
462                 ast_test_status_update(test, "Bad a line 2\n");
463                 res = AST_TEST_FAIL;
464                 goto cleanup;
465         } else if (!verify_a_line(sdp, 0, 2, "rtpmap", "97 iLBC/8000")) {
466                 ast_test_status_update(test, "Bad a line 3\n");
467                 res = AST_TEST_FAIL;
468                 goto cleanup;
469         } else if (!verify_a_line(sdp, 0, 3, "sendrecv", "")) {
470                 ast_test_status_update(test, "Bad a line 3\n");
471                 res = AST_TEST_FAIL;
472                 goto cleanup;
473         } else if (!verify_m_line(sdp, 1, "video", 51372, 1, "RTP/AVP", "31", "32", NULL)) {
474                 ast_test_status_update(test, "Bad m line 2\n");
475                 res = AST_TEST_FAIL;
476                 goto cleanup;
477         } else if (!verify_a_line(sdp, 1, 0, "rtpmap", "31 H261/90000")) {
478                 ast_test_status_update(test, "Bad a line 4\n");
479                 res = AST_TEST_FAIL;
480                 goto cleanup;
481         } else if (!verify_a_line(sdp, 1, 1, "rtpmap", "32 MPV/90000")) {
482                 ast_test_status_update(test, "Bad a line 5\n");
483                 res = AST_TEST_FAIL;
484                 goto cleanup;
485         }
486
487 cleanup:
488         ao2_cleanup(sdp);
489         ast_sdp_translator_free(translator);
490         pj_pool_release(pool);
491         return res;
492 }
493
494 AST_TEST_DEFINE(sdp_to_pjmedia_test)
495 {
496         struct ast_sdp_translator *translator;
497         char *sdp_str =
498       "v=0\r\n"
499       "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
500       "s= \r\n"
501       "c=IN IP4 host.atlanta.example.com\r\n"
502       "t=123 456\r\n"
503       "m=audio 49170 RTP/AVP 0 8 97\r\n"
504       "a=rtpmap:0 PCMU/8000\r\n"
505       "a=rtpmap:8 PCMA/8000\r\n"
506       "a=rtpmap:97 iLBC/8000\r\n"
507           "a=sendrecv\r\n"
508       "m=video 51372 RTP/AVP 31 32\r\n"
509       "a=rtpmap:31 H261/90000\r\n"
510       "a=rtpmap:32 MPV/90000\r\n\r\n";
511         pj_pool_t *pool;
512         pjmedia_sdp_session *pjmedia_sdp_orig;
513         const pjmedia_sdp_session *pjmedia_sdp_dup;
514         struct ast_sdp *sdp = NULL;
515         pj_status_t status;
516         enum ast_test_result_state res = AST_TEST_PASS;
517         char buf[2048];
518         char errbuf[256];
519
520         switch (cmd) {
521         case TEST_INIT:
522                 info->name = "sdp_to_pjmedia";
523                 info->category = "/main/sdp/";
524                 info->summary = "SDP to PJMEDIA unit test";
525                 info->description =
526                         "Ensures PJMEDIA SDPs are translated correctly";
527                 return AST_TEST_NOT_RUN;
528         case TEST_EXECUTE:
529                 break;
530         }
531
532         pool = pj_pool_create(&sdp_caching_pool.factory, "pjmedia to sdp test", 1024, 1024, NULL);
533
534         translator = ast_sdp_translator_new(AST_SDP_IMPL_PJMEDIA);
535         if (!translator) {
536                 ast_test_status_update(test, "Failed to create SDP translator\n");
537                 res = AST_TEST_FAIL;
538                 goto cleanup;
539         }
540
541         status = pjmedia_sdp_parse(pool, sdp_str, strlen(sdp_str), &pjmedia_sdp_orig);
542         if (status != PJ_SUCCESS) {
543                 ast_test_status_update(test, "Error parsing SDP\n");
544                 res = AST_TEST_FAIL;
545                 goto cleanup;
546         }
547
548         sdp = ast_sdp_translator_to_sdp(translator, pjmedia_sdp_orig);
549         pjmedia_sdp_dup = ast_sdp_translator_from_sdp(translator, sdp);
550
551         if ((status = pjmedia_sdp_session_cmp(pjmedia_sdp_orig, pjmedia_sdp_dup, 0)) != PJ_SUCCESS) {
552                 ast_test_status_update(test, "SDPs aren't equal\n");
553                 pjmedia_sdp_print(pjmedia_sdp_orig, buf, sizeof(buf));
554                 ast_test_status_update(test, "Original SDP is %s\n", buf);
555                 pjmedia_sdp_print(pjmedia_sdp_dup, buf, sizeof(buf));
556                 ast_test_status_update(test, "New SDP is %s\n", buf);
557                 pjmedia_strerror(status, errbuf, sizeof(errbuf));
558                 ast_test_status_update(test, "PJMEDIA says %d: '%s'\n", status, errbuf);
559                 res = AST_TEST_FAIL;
560                 goto cleanup;
561         }
562
563 cleanup:
564         ao2_cleanup(sdp);
565         ast_sdp_translator_free(translator);
566         pj_pool_release(pool);
567         return res;
568 }
569
570 #endif /* TEST_FRAMEWORK */
571
572 static int load_module(void)
573 {
574         if (ast_sdp_register_translator(&pjmedia_translator)) {
575                 return AST_MODULE_LOAD_DECLINE;
576         }
577         ast_pjproject_caching_pool_init(&sdp_caching_pool, NULL, 1024 * 1024);
578         AST_TEST_REGISTER(pjmedia_to_sdp_test);
579         AST_TEST_REGISTER(sdp_to_pjmedia_test);
580
581         return AST_MODULE_LOAD_SUCCESS;
582 }
583
584 static int unload_module(void)
585 {
586         ast_sdp_unregister_translator(&pjmedia_translator);
587         ast_pjproject_caching_pool_destroy(&sdp_caching_pool);
588         AST_TEST_UNREGISTER(pjmedia_to_sdp_test);
589         AST_TEST_UNREGISTER(sdp_to_pjmedia_test);
590         return 0;
591 }
592
593 static int reload_module(void)
594 {
595         return 0;
596 }
597
598 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJMEDIA SDP Translator",
599         .support_level = AST_MODULE_SUPPORT_CORE,
600         .load = load_module,
601         .unload = unload_module,
602         .reload = reload_module,
603         .load_pri = AST_MODPRI_CHANNEL_DEPEND,
604         .requires = "res_pjproject",
605 );