SDP: Ensure SDPs "merge" properly.
[asterisk/asterisk.git] / tests / test_sdp.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2017, Digium Inc.
5  *
6  * Mark Michelson <mmmichelson@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 /*** MODULEINFO
20         <depend>TEST_FRAMEWORK</depend>
21         <support_level>core</support_level>
22  ***/
23
24 #include "asterisk.h"
25 #include "asterisk/test.h"
26 #include "asterisk/module.h"
27 #include "asterisk/sdp.h"
28 #include "asterisk/stream.h"
29 #include "asterisk/format.h"
30 #include "asterisk/format_cache.h"
31 #include "asterisk/format_cap.h"
32
33 static int validate_o_line(struct ast_test *test, const struct ast_sdp_o_line *o_line,
34         const char *sdpowner, const char *address_type, const char *address)
35 {
36         if (!o_line) {
37                 return -1;
38         }
39
40         if (strcmp(o_line->username, sdpowner)) {
41                 ast_test_status_update(test, "Expected o-line SDP owner %s but got %s\n",
42                         sdpowner, o_line->username);
43                 return -1;
44         }
45
46         if (strcmp(o_line->address_type, address_type)) {
47                 ast_test_status_update(test, "Expected o-line SDP address type %s but got %s\n",
48                         address_type, o_line->address_type);
49                 return -1;
50         }
51
52         if (strcmp(o_line->address, address)) {
53                 ast_test_status_update(test, "Expected o-line SDP address %s but got %s\n",
54                         address, o_line->address);
55                 return -1;
56         }
57
58         ast_test_status_update(test, "SDP o-line is as expected!\n");
59         return 0;
60 }
61
62 static int validate_c_line(struct ast_test *test, const struct ast_sdp_c_line *c_line,
63         const char *address_type, const char *address)
64 {
65         if (strcmp(c_line->address_type, address_type)) {
66                 ast_test_status_update(test, "Expected c-line SDP address type %s but got %s\n",
67                         address_type, c_line->address_type);
68                 return -1;
69         }
70
71         if (strcmp(c_line->address, address)) {
72                 ast_test_status_update(test, "Expected c-line SDP address %s but got %s\n",
73                         address, c_line->address);
74                 return -1;
75         }
76
77         ast_test_status_update(test, "SDP c-line is as expected!\n");
78         return 0;
79 }
80
81 static int validate_m_line(struct ast_test *test, const struct ast_sdp_m_line *m_line,
82         const char *media_type, int num_payloads)
83 {
84         if (strcmp(m_line->type, media_type)) {
85                 ast_test_status_update(test, "Expected m-line media type %s but got %s\n",
86                         media_type, m_line->type);
87                 return -1;
88         }
89
90         if (ast_sdp_m_get_payload_count(m_line) != num_payloads) {
91                 ast_test_status_update(test, "Expected m-line payload count %d but got %d\n",
92                         num_payloads, ast_sdp_m_get_payload_count(m_line));
93                 return -1;
94         }
95
96         ast_test_status_update(test, "SDP m-line is as expected\n");
97         return 0;
98 }
99
100 static int validate_rtpmap(struct ast_test *test, const struct ast_sdp_m_line *m_line,
101         const char *media_name)
102 {
103         struct ast_sdp_a_line *a_line;
104         int i;
105
106         for (i = 0; i < ast_sdp_m_get_a_count(m_line); ++i) {
107                 struct ast_sdp_rtpmap *rtpmap;
108                 int match;
109
110                 a_line = ast_sdp_m_get_a(m_line, i);
111                 if (strcmp(a_line->name, "rtpmap")) {
112                         continue;
113                 }
114
115                 rtpmap = ast_sdp_a_get_rtpmap(a_line);
116                 if (!rtpmap) {
117                         return -1;
118                 }
119
120                 match = !strcmp(rtpmap->encoding_name, media_name);
121
122                 ast_sdp_rtpmap_free(rtpmap);
123                 if (match) {
124                         return 0;
125                 }
126         }
127
128         ast_test_status_update(test, "Could not find rtpmap with encoding name %s\n", media_name);
129
130         return -1;
131 }
132
133 AST_TEST_DEFINE(invalid_rtpmap)
134 {
135         /* a=rtpmap: is already assumed. This is the part after that */
136         static const char *invalids[] = {
137                 "J PCMU/8000",
138                 "0 PCMU:8000",
139                 "0 PCMU/EIGHT-THOUSAND",
140                 "0 PCMU/8000million/2",
141                 "0 PCMU//2",
142                 "0 /8000/2",
143                 "0 PCMU/8000/",
144                 "0 PCMU/8000million",
145         };
146         int i;
147         enum ast_test_result_state res = AST_TEST_PASS;
148
149         switch(cmd) {
150         case TEST_INIT:
151                 info->name = "invalid_rtpmap";
152                 info->category = "/main/sdp/";
153                 info->summary = "Ensure invalid rtpmaps are rejected";
154                 info->description =
155                         "Try to convert several invalid rtpmap attributes. If\n"
156                         "any succeeds, the test fails.";
157                 return AST_TEST_NOT_RUN;
158         case TEST_EXECUTE:
159                 break;
160         }
161
162         for (i = 0; i < ARRAY_LEN(invalids); ++i) {
163                 struct ast_sdp_a_line *a_line;
164                 struct ast_sdp_rtpmap *rtpmap;
165
166                 a_line = ast_sdp_a_alloc("rtpmap", invalids[i]);
167                 rtpmap = ast_sdp_a_get_rtpmap(a_line);
168                 if (rtpmap) {
169                         ast_test_status_update(test, "Invalid rtpmap '%s' was accepted as valid\n",
170                                 invalids[i]);
171                         res = AST_TEST_FAIL;
172                 }
173                 ast_sdp_a_free(a_line);
174                 ast_sdp_rtpmap_free(rtpmap);
175         }
176
177         return res;
178 }
179
180 AST_TEST_DEFINE(rtpmap)
181 {
182         static const char *valids[] = {
183                 "0 PCMU/8000",
184                 "107 opus/48000/2",
185         };
186         static int payloads[] = {
187                 0,
188                 107,
189         };
190         static const char *encoding_names[] = {
191                 "PCMU",
192                 "opus",
193         };
194         static int clock_rates[] = {
195                 8000,
196                 48000,
197         };
198         static const char *encoding_parameters[] = {
199                 "",
200                 "2",
201         };
202         int i;
203         enum ast_test_result_state res = AST_TEST_PASS;
204
205         switch(cmd) {
206         case TEST_INIT:
207                 info->name = "rtpmap";
208                 info->category = "/main/sdp/";
209                 info->summary = "Ensure rtpmap attribute values are parsed correctly";
210                 info->description =
211                         "Parse several valid rtpmap attributes. Ensure that the parsed values\n"
212                         "are what we expect";
213                 return AST_TEST_NOT_RUN;
214         case TEST_EXECUTE:
215                 break;
216         }
217
218         for (i = 0; i < ARRAY_LEN(valids); ++i) {
219                 struct ast_sdp_a_line *a_line;
220                 struct ast_sdp_rtpmap *rtpmap;
221
222                 a_line = ast_sdp_a_alloc("rtpmap", valids[i]);
223                 rtpmap = ast_sdp_a_get_rtpmap(a_line);
224                 if (!rtpmap) {
225                         ast_test_status_update(test, "Valid rtpmap '%s' was rejected as invalid\n",
226                                 valids[i]);
227                         res = AST_TEST_FAIL;
228                         continue;
229                 }
230                 if (rtpmap->payload != payloads[i]) {
231                         ast_test_status_update(test, "RTPmap payload '%d' does not match expected '%d'\n",
232                                 rtpmap->payload, payloads[i]);
233                         res = AST_TEST_FAIL;
234                 }
235                 if (strcmp(rtpmap->encoding_name, encoding_names[i])) {
236                         ast_test_status_update(test, "RTPmap encoding_name '%s' does not match expected '%s'\n",
237                                 rtpmap->encoding_name, encoding_names[i]);
238                         res = AST_TEST_FAIL;
239                 }
240                 if (rtpmap->clock_rate != clock_rates[i]) {
241                         ast_test_status_update(test, "RTPmap clock rate '%d' does not match expected '%d'\n",
242                                 rtpmap->clock_rate, clock_rates[i]);
243                         res = AST_TEST_FAIL;
244                 }
245                 if (strcmp(rtpmap->encoding_parameters, encoding_parameters[i])) {
246                         ast_test_status_update(test, "RTPmap encoding_parameter '%s' does not match expected '%s'\n",
247                                 rtpmap->encoding_parameters, encoding_parameters[i]);
248                         res = AST_TEST_FAIL;
249                 }
250                 ast_sdp_a_free(a_line);
251                 ast_sdp_rtpmap_free(rtpmap);
252         }
253
254         return res;
255 }
256
257 AST_TEST_DEFINE(find_attr)
258 {
259         enum ast_test_result_state res = AST_TEST_PASS;
260         struct ast_sdp_m_line *m_line;
261         struct ast_sdp_a_line *a_line;
262
263         switch(cmd) {
264         case TEST_INIT:
265                 info->name = "find_attr";
266                 info->category = "/main/sdp/";
267                 info->summary = "Ensure that finding attributes works as expected";
268                 info->description =
269                         "An SDP m-line is created, and two attributes are added.\n"
270                         "We then attempt a series of attribute-finding calls that are expected to work\n"
271                         "followed by a series of attribute-finding calls that are expected fo fail.";
272                 return AST_TEST_NOT_RUN;
273         case TEST_EXECUTE:
274                 break;
275         }
276
277         m_line = ast_sdp_m_alloc("audio", 666, 1, "RTP/AVP", NULL);
278         if (!m_line) {
279                 res = AST_TEST_FAIL;
280                 goto end;
281         }
282         a_line = ast_sdp_a_alloc("foo", "0 bar");
283         if (!a_line) {
284                 res = AST_TEST_FAIL;
285                 goto end;
286         }
287         ast_sdp_m_add_a(m_line, a_line);
288
289         a_line = ast_sdp_a_alloc("baz", "howdy");
290         if (!a_line) {
291                 res = AST_TEST_FAIL;
292                 goto end;
293         }
294         ast_sdp_m_add_a(m_line, a_line);
295
296         /* These should work */
297         a_line = ast_sdp_m_find_attribute(m_line, "foo", 0);
298         if (!a_line) {
299                 ast_test_status_update(test, "Failed to find attribute 'foo' with payload '0'\n");
300                 res = AST_TEST_FAIL;
301         }
302         a_line = ast_sdp_m_find_attribute(m_line, "foo", -1);
303         if (!a_line) {
304                 ast_test_status_update(test, "Failed to find attribute 'foo' with unspecified payload\n");
305                 res = AST_TEST_FAIL;
306         }
307         a_line = ast_sdp_m_find_attribute(m_line, "baz", -1);
308         if (!a_line) {
309                 ast_test_status_update(test, "Failed to find attribute 'baz' with unspecified payload\n");
310                 res = AST_TEST_FAIL;
311         }
312
313         /* These should fail */
314         a_line = ast_sdp_m_find_attribute(m_line, "foo", 1);
315         if (a_line) {
316                 ast_test_status_update(test, "Found non-existent attribute 'foo' with payload '1'\n");
317                 res = AST_TEST_FAIL;
318         }
319         a_line = ast_sdp_m_find_attribute(m_line, "baz", 0);
320         if (a_line) {
321                 ast_test_status_update(test, "Found non-existent attribute 'baz' with payload '0'\n");
322                 res = AST_TEST_FAIL;
323         }
324         a_line = ast_sdp_m_find_attribute(m_line, "wibble", 0);
325         if (a_line) {
326                 ast_test_status_update(test, "Found non-existent attribute 'wibble' with payload '0'\n");
327                 res = AST_TEST_FAIL;
328         }
329         a_line = ast_sdp_m_find_attribute(m_line, "wibble", -1);
330         if (a_line) {
331                 ast_test_status_update(test, "Found non-existent attribute 'foo' with unspecified payload\n");
332                 res = AST_TEST_FAIL;
333         }
334
335 end:
336         ast_sdp_m_free(m_line);
337         return res;
338 }
339
340 struct sdp_format {
341         enum ast_media_type type;
342         const char *formats;
343 };
344
345 static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_format *formats)
346 {
347         struct ast_stream_topology *topology = NULL;
348         struct ast_sdp_state *state = NULL;
349         struct ast_sdp_options *options;
350         int i;
351
352         options = ast_sdp_options_alloc();
353         if (!options) {
354                 goto end;
355         }
356         ast_sdp_options_set_media_address(options, "127.0.0.1");
357         ast_sdp_options_set_sdpowner(options, "me");
358         ast_sdp_options_set_rtp_engine(options, "asterisk");
359         ast_sdp_options_set_impl(options, AST_SDP_IMPL_PJMEDIA);
360
361         topology = ast_stream_topology_alloc();
362         if (!topology) {
363                 goto end;
364         }
365
366         for (i = 0; i < num_streams; ++i) {
367                 RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
368                 struct ast_stream *stream;
369
370                 caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
371                 if (!caps) {
372                         goto end;
373                 }
374                 if (ast_format_cap_update_by_allow_disallow(caps, formats[i].formats, 1) < 0) {
375                         goto end;
376                 }
377                 stream = ast_stream_alloc("sure_thing", formats[i].type);
378                 if (!stream) {
379                         goto end;
380                 }
381                 ast_stream_set_formats(stream, caps);
382                 ast_stream_topology_append_stream(topology, stream);
383         }
384
385         state = ast_sdp_state_alloc(topology, options);
386         if (!state) {
387                 goto end;
388         }
389
390 end:
391         ast_stream_topology_free(topology);
392         if (!state) {
393                 ast_sdp_options_free(options);
394         }
395
396         return state;
397 }
398
399 AST_TEST_DEFINE(topology_to_sdp)
400 {
401         enum ast_test_result_state res = AST_TEST_FAIL;
402         struct ast_sdp_state *sdp_state = NULL;
403         const struct ast_sdp *sdp = NULL;
404         struct ast_sdp_m_line *m_line = NULL;
405         struct sdp_format formats[] = {
406                 { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" },
407                 { AST_MEDIA_TYPE_VIDEO, "h264,vp8" },
408         };
409
410         switch(cmd) {
411         case TEST_INIT:
412                 info->name = "topology_to_sdp";
413                 info->category = "/main/sdp/";
414                 info->summary = "Convert a topology into an SDP";
415                 info->description =
416                         "Ensure SDPs get converted to expected stream topology";
417                 return AST_TEST_NOT_RUN;
418         case TEST_EXECUTE:
419                 break;
420         }
421
422         sdp_state = build_sdp_state(ARRAY_LEN(formats), formats);
423         if (!sdp_state) {
424                 goto end;
425         }
426
427         sdp = ast_sdp_state_get_local_sdp(sdp_state);
428         if (!sdp) {
429                 goto end;
430         }
431
432         if (validate_o_line(test, sdp->o_line, "me", "IP4", "127.0.0.1")) {
433                 goto end;
434         }
435
436         if (validate_c_line(test, sdp->c_line, "IP4", "127.0.0.1")) {
437                 goto end;
438         }
439
440         if (ast_sdp_get_m_count(sdp) != 2) {
441                 ast_test_status_update(test, "Unexpected number of streams in generated SDP: %d\n",
442                         ast_sdp_get_m_count(sdp));
443                 goto end;
444         }
445
446         m_line = ast_sdp_get_m(sdp, 0);
447
448         if (validate_m_line(test, m_line, "audio", 4)) {
449                 goto end;
450         }
451
452         if (validate_rtpmap(test, m_line, "PCMU")) {
453                 goto end;
454         }
455
456         if (validate_rtpmap(test, m_line, "PCMA")) {
457                 goto end;
458         }
459
460         if (validate_rtpmap(test, m_line, "G722")) {
461                 goto end;
462         }
463
464         if (validate_rtpmap(test, m_line, "opus")) {
465                 goto end;
466         }
467
468         m_line = ast_sdp_get_m(sdp, 1);
469         if (validate_m_line(test, m_line, "video", 2)) {
470                 goto end;
471         }
472
473         if (validate_rtpmap(test, m_line, "VP8")) {
474                 goto end;
475         }
476
477         if (validate_rtpmap(test, m_line, "H264")) {
478                 goto end;
479         }
480
481         res = AST_TEST_PASS;
482
483 end:
484         ast_sdp_state_free(sdp_state);
485         return res;
486 }
487
488 static int validate_formats(struct ast_test *test, struct ast_stream_topology *topology, int index,
489         enum ast_media_type type, int format_count, const char **expected_formats)
490 {
491         struct ast_stream *stream;
492         struct ast_format_cap *caps;
493         struct ast_format *format;
494         int i;
495
496         stream = ast_stream_topology_get_stream(topology, index);
497         if (ast_stream_get_type(stream) != type) {
498                 ast_test_status_update(test, "Unexpected stream type encountered\n");
499                 return -1;
500         }
501         caps = ast_stream_get_formats(stream);
502
503         if (ast_format_cap_count(caps) != format_count) {
504                 ast_test_status_update(test, "Unexpected format count '%d'. Expecting '%d'\n",
505                         (int) ast_format_cap_count(caps), format_count);
506                 return -1;
507         }
508
509         for (i = 0; i < ast_format_cap_count(caps); ++i) {
510                 format = ast_format_cap_get_format(caps, i);
511                 if (strcmp(ast_format_get_name(format), expected_formats[i])) {
512                         ast_test_status_update(test, "Unexpected format '%s'at index %d. Expected '%s'\n",
513                                 ast_format_get_name(format), i, expected_formats[i]);
514                         return -1;
515                 }
516         }
517
518         return 0;
519 }
520
521 AST_TEST_DEFINE(sdp_to_topology)
522 {
523         enum ast_test_result_state res = AST_TEST_PASS;
524         struct ast_sdp_state *sdp_state;
525         const struct ast_sdp *sdp;
526         struct ast_stream_topology *topology = NULL;
527         struct sdp_format sdp_formats[] = {
528                 { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" },
529                 { AST_MEDIA_TYPE_VIDEO, "h264,vp8" },
530         };
531         static const char *expected_audio_formats[] = {
532                 "ulaw",
533                 "alaw",
534                 "g722",
535                 "opus",
536         };
537         static const char *expected_video_formats[] = {
538                 "h264",
539                 "vp8",
540         };
541
542         switch(cmd) {
543         case TEST_INIT:
544                 info->name = "sdp_to_topology";
545                 info->category = "/main/sdp/";
546                 info->summary = "Convert an SDP into a topology";
547                 info->description =
548                         "Ensure SDPs get converted to expected stream topology";
549                 return AST_TEST_NOT_RUN;
550         case TEST_EXECUTE:
551                 break;
552         }
553
554         sdp_state = build_sdp_state(ARRAY_LEN(sdp_formats), sdp_formats);
555         if (!sdp_state) {
556                 res = AST_TEST_FAIL;
557                 goto end;
558         }
559
560         sdp = ast_sdp_state_get_local_sdp(sdp_state);
561         if (!sdp) {
562                 res = AST_TEST_FAIL;
563                 goto end;
564         }
565
566         topology = ast_get_topology_from_sdp(sdp);
567
568         if (ast_stream_topology_get_count(topology) != 2) {
569                 ast_test_status_update(test, "Unexpected topology count '%d'. Expecting 2\n",
570                         ast_stream_topology_get_count(topology));
571                 res = AST_TEST_FAIL;
572                 goto end;
573         }
574
575         if (validate_formats(test, topology, 0, AST_MEDIA_TYPE_AUDIO,
576                         ARRAY_LEN(expected_audio_formats), expected_audio_formats)) {
577                 res = AST_TEST_FAIL;
578                 goto end;
579         }
580
581         if (validate_formats(test, topology, 1, AST_MEDIA_TYPE_VIDEO,
582                         ARRAY_LEN(expected_video_formats), expected_video_formats)) {
583                 res = AST_TEST_FAIL;
584                 goto end;
585         }
586
587 end:
588         ast_sdp_state_free(sdp_state);
589         ast_stream_topology_free(topology);
590         return res;
591 }
592
593 static int validate_merged_sdp(struct ast_test *test, const struct ast_sdp *sdp)
594 {
595         struct ast_sdp_m_line *m_line;
596
597         if (!sdp) {
598                 return -1;
599         }
600
601         m_line = ast_sdp_get_m(sdp, 0);
602
603         if (validate_m_line(test, m_line, "audio", 1)) {
604                 return -1;
605         }
606
607         if (validate_rtpmap(test, m_line, "PCMU")) {
608                 return -1;
609         }
610
611         /* The other audio formats should *NOT* be present */
612         if (!validate_rtpmap(test, m_line, "PCMA")) {
613                 return -1;
614         }
615
616         if (!validate_rtpmap(test, m_line, "G722")) {
617                 return -1;
618         }
619
620         if (!validate_rtpmap(test, m_line, "opus")) {
621                 return -1;
622         }
623
624         m_line = ast_sdp_get_m(sdp, 1);
625
626         if (validate_m_line(test, m_line, "video", 1)) {
627                 return -1;
628         }
629
630         if (validate_rtpmap(test, m_line, "VP8")) {
631                 return -1;
632         }
633
634         if (!validate_rtpmap(test, m_line, "H264")) {
635                 return -1;
636         }
637
638         return 0;
639 }
640
641 AST_TEST_DEFINE(sdp_merge_symmetric)
642 {
643         enum ast_test_result_state res = AST_TEST_PASS;
644         struct ast_sdp_state *sdp_state_offerer = NULL;
645         struct ast_sdp_state *sdp_state_answerer = NULL;
646         const struct ast_sdp *offerer_sdp;
647         const struct ast_sdp *answerer_sdp;
648
649         static const struct sdp_format offerer_formats[] = {
650                 { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" },
651                 { AST_MEDIA_TYPE_VIDEO, "h264,vp8" },
652         };
653         static const struct sdp_format answerer_formats[] = {
654                 { AST_MEDIA_TYPE_AUDIO, "ulaw" },
655                 { AST_MEDIA_TYPE_VIDEO, "vp8" },
656         };
657
658         switch(cmd) {
659         case TEST_INIT:
660                 info->name = "sdp_merge_symmetric";
661                 info->category = "/main/sdp/";
662                 info->summary = "Merge two SDPs with symmetric stream types";
663                 info->description =
664                         "SDPs 1 and 2 each have one audio and one video stream (in that order).\n"
665                         "SDP 1 offers to SDP 2, who answers. We ensure that both local SDPs have\n"
666                         "the expected stream types and the expected formats";
667                 return AST_TEST_NOT_RUN;
668         case TEST_EXECUTE:
669                 break;
670         }
671
672         sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats);
673         if (!sdp_state_offerer) {
674                 res = AST_TEST_FAIL;
675                 goto end;
676         }
677
678         sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats);
679         if (!sdp_state_answerer) {
680                 res = AST_TEST_FAIL;
681                 goto end;
682         }
683
684         offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer);
685         if (!offerer_sdp) {
686                 res = AST_TEST_FAIL;
687                 goto end;
688         }
689
690         ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp);
691         answerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_answerer);
692         if (!answerer_sdp) {
693                 res = AST_TEST_FAIL;
694                 goto end;
695         }
696
697         ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp);
698
699         /* Get the offerer SDP again because it's now going to be the joint SDP */
700         offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer);
701         if (validate_merged_sdp(test, offerer_sdp)) {
702                 res = AST_TEST_FAIL;
703                 goto end;
704         }
705         if (validate_merged_sdp(test, answerer_sdp)) {
706                 res = AST_TEST_FAIL;
707                 goto end;
708         }
709
710 end:
711         ast_sdp_state_free(sdp_state_offerer);
712         ast_sdp_state_free(sdp_state_answerer);
713
714         return res;
715 }
716
717 AST_TEST_DEFINE(sdp_merge_crisscross)
718 {
719         enum ast_test_result_state res = AST_TEST_PASS;
720         struct ast_sdp_state *sdp_state_offerer = NULL;
721         struct ast_sdp_state *sdp_state_answerer = NULL;
722         const struct ast_sdp *offerer_sdp;
723         const struct ast_sdp *answerer_sdp;
724
725         static const struct sdp_format offerer_formats[] = {
726                 { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" },
727                 { AST_MEDIA_TYPE_VIDEO, "h264,vp8" },
728         };
729         static const struct sdp_format answerer_formats[] = {
730                 { AST_MEDIA_TYPE_VIDEO, "vp8" },
731                 { AST_MEDIA_TYPE_AUDIO, "ulaw" },
732         };
733
734         switch(cmd) {
735         case TEST_INIT:
736                 info->name = "sdp_merge_crisscross";
737                 info->category = "/main/sdp/";
738                 info->summary = "Merge two SDPs with symmetric stream types";
739                 info->description =
740                         "SDPs 1 and 2 each have one audio and one video stream. However, SDP 1 and\n"
741                         "2 natively have the formats in a different order.\n"
742                         "SDP 1 offers to SDP 2, who answers. We ensure that both local SDPs have\n"
743                         "the expected stream types and the expected formats. Since SDP 1 was the\n"
744                         "offerer, the format order on SDP 1 should determine the order of formats in the SDPs";
745                 return AST_TEST_NOT_RUN;
746         case TEST_EXECUTE:
747                 break;
748         }
749
750         sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats);
751         if (!sdp_state_offerer) {
752                 res = AST_TEST_FAIL;
753                 goto end;
754         }
755
756         sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats);
757         if (!sdp_state_answerer) {
758                 res = AST_TEST_FAIL;
759                 goto end;
760         }
761
762         offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer);
763         if (!offerer_sdp) {
764                 res = AST_TEST_FAIL;
765                 goto end;
766         }
767
768         ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp);
769         answerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_answerer);
770         if (!answerer_sdp) {
771                 res = AST_TEST_FAIL;
772                 goto end;
773         }
774
775         ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp);
776
777         /* Get the offerer SDP again because it's now going to be the joint SDP */
778         offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer);
779         if (validate_merged_sdp(test, offerer_sdp)) {
780                 res = AST_TEST_FAIL;
781                 goto end;
782         }
783         if (validate_merged_sdp(test, answerer_sdp)) {
784                 res = AST_TEST_FAIL;
785                 goto end;
786         }
787
788 end:
789         ast_sdp_state_free(sdp_state_offerer);
790         ast_sdp_state_free(sdp_state_answerer);
791
792         return res;
793 }
794
795 static int unload_module(void)
796 {
797         AST_TEST_UNREGISTER(invalid_rtpmap);
798         AST_TEST_UNREGISTER(rtpmap);
799         AST_TEST_UNREGISTER(find_attr);
800         AST_TEST_UNREGISTER(topology_to_sdp);
801         AST_TEST_UNREGISTER(sdp_to_topology);
802         AST_TEST_UNREGISTER(sdp_merge_symmetric);
803         AST_TEST_UNREGISTER(sdp_merge_crisscross);
804
805         return 0;
806 }
807
808 static int load_module(void)
809 {
810         AST_TEST_REGISTER(invalid_rtpmap);
811         AST_TEST_REGISTER(rtpmap);
812         AST_TEST_REGISTER(find_attr);
813         AST_TEST_REGISTER(topology_to_sdp);
814         AST_TEST_REGISTER(sdp_to_topology);
815         AST_TEST_REGISTER(sdp_merge_symmetric);
816         AST_TEST_REGISTER(sdp_merge_crisscross);
817
818         return AST_MODULE_LOAD_SUCCESS;
819 }
820
821 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "SDP tests");