add sdp_msg API: SDP parsing/composition

Rationale: in order to add full SDP to the MNCC protocol (upcoming patch
I8c3b2de53ffae4ec3a66b9dabf308c290a2c999f), we need to parse and compose SDP
messages. Obviously, libosmo-mgcp-client already contains similar code, but
that is unfortunately heavily glued to the actual MGCP implementation. The
simplest solution is to create this separate implementation, copy-pasting from
the existing libosmo-mgcp-client code as is convenient.

This API is added here to probe whether it works well. When it does, the
intention is to "move it up" to osmo-mgw and overhaul the SDP parsing in our
MGCP client and MGCP server APIs using this same API.

Change-Id: If3ce23cd5bab15e2ab4c52ef3e4c75979dffe931
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 27c1205..864ac7c 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -2,6 +2,7 @@
 	sms_queue \
 	msc_vlr \
 	db_sms \
+	sdp_msg \
 	$(NULL)
 
 if BUILD_SMPP
diff --git a/tests/sdp_msg/Makefile.am b/tests/sdp_msg/Makefile.am
new file mode 100644
index 0000000..7d8def9
--- /dev/null
+++ b/tests/sdp_msg/Makefile.am
@@ -0,0 +1,32 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	-ggdb3 \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(NULL)
+
+LDADD = \
+	$(top_builddir)/src/libmsc/libmsc.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(NULL)
+
+EXTRA_DIST = \
+	sdp_msg_test.ok \
+	sdp_msg_test.err \
+	$(NULL)
+
+noinst_PROGRAMS = \
+	sdp_msg_test \
+	$(NULL)
+
+sdp_msg_test_SOURCES = \
+	sdp_msg_test.c \
+	$(NULL)
+
+.PHONY: update_exp
+update_exp:
+	$(builddir)/sdp_msg_test >$(srcdir)/sdp_msg_test.ok 2>$(srcdir)/sdp_msg_test.err
diff --git a/tests/sdp_msg/sdp_msg_test.c b/tests/sdp_msg/sdp_msg_test.c
new file mode 100644
index 0000000..b97127a
--- /dev/null
+++ b/tests/sdp_msg/sdp_msg_test.c
@@ -0,0 +1,569 @@
+#include <stdio.h>
+#include <string.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/msc/sdp_msg.h>
+
+struct sdp_test_data {
+	const char *sdp_input;
+	const char *expect_sdp_str;
+};
+
+static void dump_sdp(const char *str, const char *prefix)
+{
+	while (str && *str) {
+		const char *line_end = sdp_msg_line_end(str);
+		while (*line_end == '\r' || *line_end == '\n')
+			line_end++;
+		printf("%s%s\n", prefix, osmo_escape_str(str, line_end - str));
+		str = line_end;
+	}
+}
+
+struct sdp_test_data sdp_tests[] = {
+	{
+		"v=0\r\n"
+		"o=- 5628250 5628250 IN IP4 192.168.11.121\r\n"
+		"s=-\r\n"
+		"c=IN IP4 192.168.11.121\r\n"
+		"t=0 0\r\n"
+		"m=audio 10020 RTP/AVP 18 0 2 4 8 96 97 98 100 101\r\n"
+		"a=rtpmap:18 G729/8000\r\n"
+		"a=rtpmap:0 PCMU/8000\r\n"
+		"a=rtpmap:2 G726-32/8000\r\n"
+		"a=rtpmap:4 G723/8000\r\n"
+		"a=rtpmap:8 PCMA/8000\r\n"
+		"a=rtpmap:96 G726-40/8000\r\n"
+		"a=rtpmap:97 G726-24/8000\r\n"
+		"a=rtpmap:98 G726-16/8000\r\n"
+		"a=rtpmap:100 NSE/8000\r\n"
+		"a=fmtp:100 192-193\r\n"
+		"a=rtpmap:101 telephone-event/8000\r\n"
+		"a=fmtp:101 0-15\r\n"
+		"a=ptime:20\r\n"
+		"a=sendrecv\r\n"
+		,
+		"v=0\r\n"
+		"o=OsmoMSC 0 0 IN IP4 192.168.11.121\r\n"
+		"s=GSM Call\r\n"
+		"c=IN IP4 192.168.11.121\r\n"
+		"t=0 0\r\n"
+		"m=audio 10020 RTP/AVP 18 0 2 4 8 96 97 98 100 101\r\n"
+		"a=rtpmap:18 G729/8000\r\n"
+		"a=rtpmap:0 PCMU/8000\r\n"
+		"a=rtpmap:2 G726-32/8000\r\n"
+		"a=rtpmap:4 G723/8000\r\n"
+		"a=rtpmap:8 PCMA/8000\r\n"
+		"a=rtpmap:96 G726-40/8000\r\n"
+		"a=rtpmap:97 G726-24/8000\r\n"
+		"a=rtpmap:98 G726-16/8000\r\n"
+		"a=rtpmap:100 NSE/8000\r\n"
+		"a=fmtp:100 192-193\r\n"
+		"a=rtpmap:101 telephone-event/8000\r\n"
+		"a=fmtp:101 0-15\r\n"
+		"a=ptime:20\r\n"
+		,
+	},
+	{
+		"v=0\r\n"
+		"o=FooBar 1565090289 1565090290 IN IP4 192.168.11.151\r\n"
+		"s=FooBar\r\n"
+		"c=IN IP4 192.168.11.151\r\n"
+		"t=0 0\r\n"
+		"m=audio 16398 RTP/AVP 98\r\n"
+		"a=rtpmap:98 AMR/8000\r\n"
+		"a=fmtp:98 octet-align=1; mode-set=4\r\n"
+		"a=ptime:20\r\n"
+		"a=rtcp:16399 IN IP4 192.168.11.151\r\n"
+		,
+		"v=0\r\n"
+		"o=OsmoMSC 0 0 IN IP4 192.168.11.151\r\n"
+		"s=GSM Call\r\n"
+		"c=IN IP4 192.168.11.151\r\n"
+		"t=0 0\r\n"
+		"m=audio 16398 RTP/AVP 98\r\n"
+		"a=rtpmap:98 AMR/8000\r\n"
+		"a=fmtp:98 octet-align=1; mode-set=4\r\n"
+		"a=ptime:20\r\n"
+		,
+	},
+	{
+		"v=0\r\n"
+		"o=FooBar 1565090289 1565090290 IN IP4 192.168.11.151\r\n"
+		"s=FooBar\r\n"
+		"c=IN IP4 192.168.11.140\r\n"
+		"t=0 0\r\n"
+		"m=audio 30436 RTP/AVP 18 0 4 8 101\r\n"
+		"a=rtpmap:18 G729/8000\r\n"
+		"a=rtpmap:0 PCMU/8000\r\n"
+		"a=rtpmap:4 G723/8000\r\n"
+		"a=rtpmap:8 PCMA/8000\r\n"
+		"a=rtpmap:101 telephone-event/8000\r\n"
+		"a=fmtp:101 0-15\r\n"
+		"a=sendrecv\r\n"
+		"a=rtcp:30437\r\n"
+		"a=ptime:20\r\n"
+		,
+		"v=0\r\n"
+		"o=OsmoMSC 0 0 IN IP4 192.168.11.140\r\n" /* <- NOTE: loses the 'o=' address, uses only 'c=' */
+		"s=GSM Call\r\n"
+		"c=IN IP4 192.168.11.140\r\n"
+		"t=0 0\r\n"
+		"m=audio 30436 RTP/AVP 18 0 4 8 101\r\n"
+		"a=rtpmap:18 G729/8000\r\n"
+		"a=rtpmap:0 PCMU/8000\r\n"
+		"a=rtpmap:4 G723/8000\r\n"
+		"a=rtpmap:8 PCMA/8000\r\n"
+		"a=rtpmap:101 telephone-event/8000\r\n"
+		"a=fmtp:101 0-15\r\n"
+		"a=ptime:20\r\n"
+		,
+	},
+};
+
+void test_parse_and_compose()
+{
+	int i;
+	bool ok = true;
+
+	printf("\n\n%s\n", __func__);
+
+	for (i = 0; i < ARRAY_SIZE(sdp_tests); i++) {
+		struct sdp_test_data *t = &sdp_tests[i];
+		struct sdp_msg sdp = {};
+		char str[1024];
+		printf("\n[%d]\n", i);
+		dump_sdp(t->sdp_input, "sdp input: ");
+
+		sdp_msg_from_str(&sdp, t->sdp_input);
+		sdp_msg_to_str(str, sizeof(str), &sdp);
+
+		dump_sdp(str, "sdp_msg_to_str: ");
+		if (strcmp(str, t->expect_sdp_str)) {
+			int j;
+			ok = false;
+			printf("ERROR:\n");
+			dump_sdp(t->expect_sdp_str, "expect_sdp_str: ");
+			for (j = 0; t->expect_sdp_str[j]; j++) {
+				if (t->expect_sdp_str[j] != str[j]) {
+					printf("ERROR at position %d, at:\n", j);
+					dump_sdp(str + j, "     mismatch: ");
+					break;
+				}
+			}
+		} else
+			printf("[%d] ok\n", i);
+	}
+
+	OSMO_ASSERT(ok);
+}
+
+struct sdp_intersect_test_data {
+	const char *descr;
+	const char *sdp_a;
+	const char *sdp_b;
+	const char *expect_intersection;
+};
+
+#define SDP_1 \
+		"v=0\r\n" \
+		"o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n" \
+		"s=GSM Call\r\n" \
+		"c=IN IP4 23.42.23.42\r\n" \
+		"t=0 0\r\n" \
+		"m=audio 30436 RTP/AVP 112 3 111 110\r\n" \
+		"a=rtpmap:112 AMR/8000\r\n" \
+		"a=fmtp:112 octet-align=1\r\n" \
+		"a=rtpmap:3 GSM/8000\r\n" \
+		"a=rtpmap:111 GSM-HR-08/8000\r\n" \
+		"a=rtpmap:110 GSM-EFR/8000\r\n" \
+		"a=ptime:20\r\n"
+
+#define SDP_2 \
+		"v=0\r\n" \
+		"o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n" \
+		"s=GSM Call\r\n" \
+		"c=IN IP4 23.42.23.42\r\n" \
+		"t=0 0\r\n" \
+		"m=audio 30436 RTP/AVP 112 110\r\n" \
+		"a=rtpmap:112 AMR/8000\r\n" \
+		"a=fmtp:112 octet-align=1\r\n" \
+		"a=rtpmap:110 GSM-EFR/8000\r\n" \
+		"a=ptime:20\r\n"
+
+#define SDP_3 \
+		"v=0\r\n" \
+		"o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n" \
+		"s=GSM Call\r\n" \
+		"c=IN IP4 23.42.23.42\r\n" \
+		"t=0 0\r\n" \
+		"m=audio 30436 RTP/AVP 3 111\r\n" \
+		"a=rtpmap:3 GSM/8000\r\n" \
+		"a=rtpmap:111 GSM-HR-08/8000\r\n" \
+		"a=ptime:20\r\n"
+
+
+struct sdp_intersect_test_data sdp_intersect_tests[] = {
+	{
+		"identical codecs lead to no change"
+		,
+		SDP_1
+		,
+		"c=IN IP4 5.6.7.8\r\n" \
+		"m=audio 12345 RTP/AVP 112 3 111 110\r\n"
+		"a=rtpmap:112 AMR/8000\r\n"
+		"a=fmtp:112 octet-align=1\r\n"
+		"a=rtpmap:3 GSM/8000\r\n"
+		"a=rtpmap:111 GSM-HR-08/8000\r\n"
+		"a=rtpmap:110 GSM-EFR/8000\r\n"
+		,
+		SDP_1
+	},
+	{
+		"identical codecs in different order also lead to no change"
+		,
+		SDP_1
+		,
+		"c=IN IP4 5.6.7.8\r\n" \
+		"m=audio 12345 RTP/AVP 3 110 111 112\r\n"
+		"a=rtpmap:3 GSM/8000\r\n"
+		"a=rtpmap:110 GSM-EFR/8000\r\n"
+		"a=rtpmap:111 GSM-HR-08/8000\r\n"
+		"a=rtpmap:112 AMR/8000\r\n"
+		"a=fmtp:112 octet-align=1\r\n"
+		,
+		SDP_1
+	},
+	{
+		"identical codecs with mismatching payload type numbers also lead to no change"
+		,
+		SDP_1
+		,
+		"c=IN IP4 5.6.7.8\r\n" \
+		"m=audio 12345 RTP/AVP 96 97 98 99\r\n"
+		"a=rtpmap:96 GSM/8000\r\n"
+		"a=rtpmap:97 GSM-EFR/8000\r\n"
+		"a=rtpmap:98 GSM-HR-08/8000\r\n"
+		"a=rtpmap:99 AMR/8000\r\n"
+		"a=fmtp:99 octet-align=1\r\n"
+		,
+		SDP_1
+	},
+	{
+		"identical codecs plus some extra codecs also lead to no change"
+		,
+		SDP_1
+		,
+		"c=IN IP4 5.6.7.8\r\n" \
+		"m=audio 12345 RTP/AVP 8 0 96 97 98 99\r\n"
+		"a=rtpmap:8 PCMA/8000\r\n"
+		"a=rtpmap:0 PCMU/8000\r\n"
+		"a=rtpmap:96 GSM/8000\r\n"
+		"a=rtpmap:97 GSM-EFR/8000\r\n"
+		"a=rtpmap:98 GSM-HR-08/8000\r\n"
+		"a=rtpmap:99 AMR/8000\r\n"
+		"a=fmtp:99 octet-align=1\r\n"
+		,
+		SDP_1
+	},
+	{
+		"some codecs removed",
+		SDP_1,
+		SDP_2,
+		SDP_2,
+	},
+	{
+		"other codecs removed",
+		SDP_1,
+		SDP_3,
+		SDP_3,
+	},
+	{
+		"all codecs removed",
+		SDP_1
+		,
+		"s=empty"
+		,
+		"v=0\r\n" \
+		"o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n" \
+		"s=GSM Call\r\n" \
+		"c=IN IP4 23.42.23.42\r\n" \
+		"t=0 0\r\n" \
+		"m=audio 30436 RTP/AVP\r\n" \
+		"a=ptime:20\r\n"
+	},
+	{
+		"some real world test case"
+		,
+		"v=0\r\n"
+		"o=OsmoMSC 0 0 IN IP4 0.0.0.0\r\n"
+		"s=GSM Call\r\n"
+		"c=IN IP4 0.0.0.0\r\n"
+		"t=0 0\r\n"
+		"m=audio 0 RTP/AVP 112 113 110 3 111\r\n"
+		"a=rtpmap:112 AMR/8000\r\n"
+		"a=fmtp:112 octet-align=1;mode-set=0,1,2,3\r\n"
+		"a=rtpmap:113 AMR-WB/8000\r\n"
+		"a=fmtp:113 octet-align=1\r\n"
+		"a=rtpmap:110 GSM-EFR/8000\r\n"
+		"a=rtpmap:3 GSM/8000\r\n"
+		"a=rtpmap:111 GSM-HR-08/8000\r\n"
+		"a=ptime:20\r\n"
+		,
+		"v=0\r\n"
+		"o=OsmoMSC 0 0 IN IP4 0.0.0.0\r\n"
+		"s=GSM Call\r\n"
+		"c=IN IP4 0.0.0.0\r\n"
+		"t=0 0\r\n"
+		"m=audio 0 RTP/AVP 112 113 110 3 111\r\n"
+		"a=rtpmap:112 AMR/8000\r\n"
+		"a=fmtp:112 octet-align=1;mode-set=0,1,2,3\r\n"
+		"a=rtpmap:113 AMR-WB/8000\r\n"
+		"a=fmtp:113 octet-align=1\r\n"
+		"a=rtpmap:110 GSM-EFR/8000\r\n"
+		"a=rtpmap:3 GSM/8000\r\n"
+		"a=rtpmap:111 GSM-HR-08/8000\r\n"
+		"a=ptime:20\r\n"
+		,
+		"v=0\r\n"
+		"o=OsmoMSC 0 0 IN IP4 0.0.0.0\r\n"
+		"s=GSM Call\r\n"
+		"c=IN IP4 0.0.0.0\r\n"
+		"t=0 0\r\n"
+		"m=audio 0 RTP/AVP 112 113 110 3 111\r\n"
+		"a=rtpmap:112 AMR/8000\r\n"
+		"a=fmtp:112 octet-align=1;mode-set=0,1,2,3\r\n"
+		"a=rtpmap:113 AMR-WB/8000\r\n"
+		"a=fmtp:113 octet-align=1\r\n"
+		"a=rtpmap:110 GSM-EFR/8000\r\n"
+		"a=rtpmap:3 GSM/8000\r\n"
+		"a=rtpmap:111 GSM-HR-08/8000\r\n"
+		"a=ptime:20\r\n"
+	}
+};
+
+const char *sdp_msg_logstr(const struct sdp_msg *sdp)
+{
+	static char buf[1024];
+	sdp_msg_to_str(buf, sizeof(buf), sdp);
+	return buf;
+}
+
+static void test_intersect()
+{
+	int i;
+	bool ok = true;
+	int rc;
+
+	printf("\n\n%s\n", __func__);
+
+	for (i = 0; i < ARRAY_SIZE(sdp_intersect_tests); i++) {
+		struct sdp_intersect_test_data *t = &sdp_intersect_tests[i];
+		struct sdp_msg sdp_a = {};
+		struct sdp_msg sdp_b = {};
+		char str[1024];
+		printf("\n[%d] %s\n", i, t->descr);
+		dump_sdp(t->sdp_a, "SDP A: ");
+		dump_sdp(t->sdp_b, " SDP B: ");
+
+		rc = sdp_msg_from_str(&sdp_a, t->sdp_a);
+		if (rc) {
+			printf("ERROR parsing SDP A: %d\n", rc);
+			break;
+		}
+		dump_sdp(sdp_msg_logstr(&sdp_a), "parsed SDP A: ");
+		rc = sdp_msg_from_str(&sdp_b, t->sdp_b);
+		if (rc) {
+			printf("ERROR parsing SDP A: %d\n", rc);
+			break;
+		}
+		dump_sdp(sdp_msg_logstr(&sdp_b), "parsed SDP B: ");
+		sdp_audio_codecs_intersection(&sdp_a.audio_codecs, &sdp_b.audio_codecs, false);
+		sdp_msg_to_str(str, sizeof(str), &sdp_a);
+
+		dump_sdp(str, "sdp_msg_intersection(a,b): ");
+		if (strcmp(str, t->expect_intersection)) {
+			int j;
+			ok = false;
+			printf("ERROR:\n");
+			dump_sdp(t->expect_intersection, "expect_intersection: ");
+			for (j = 0; t->expect_intersection[j]; j++) {
+				if (t->expect_intersection[j] != str[j]) {
+					printf("ERROR at position %d, at:\n", j);
+					dump_sdp(str + j, "     mismatch: ");
+					break;
+				}
+			}
+		} else
+			printf("[%d] ok\n", i);
+	}
+
+	OSMO_ASSERT(ok);
+}
+
+struct sdp_select_test_data {
+	const char *sdp;
+	unsigned int select_payload_type;
+	const char *expect_sdp;
+};
+
+struct sdp_select_test_data sdp_select_tests[] = {
+	{
+		"v=0\r\n"
+		"o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n"
+		"s=GSM Call\r\n"
+		"c=IN IP4 23.42.23.42\r\n"
+		"t=0 0\r\n"
+		"m=audio 30436 RTP/AVP 112 3 111 110\r\n"
+		"a=rtpmap:112 AMR/8000\r\n"
+		"a=fmtp:112 octet-align=1\r\n"
+		"a=rtpmap:3 GSM/8000\r\n"
+		"a=rtpmap:111 GSM-HR-08/8000\r\n"
+		"a=rtpmap:110 GSM-EFR/8000\r\n"
+		"a=ptime:20\r\n"
+		,
+		112,
+		NULL
+	},
+	{
+		"v=0\r\n"
+		"o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n"
+		"s=GSM Call\r\n"
+		"c=IN IP4 23.42.23.42\r\n"
+		"t=0 0\r\n"
+		"m=audio 30436 RTP/AVP 112 3 111 110\r\n"
+		"a=rtpmap:112 AMR/8000\r\n"
+		"a=fmtp:112 octet-align=1\r\n"
+		"a=rtpmap:3 GSM/8000\r\n"
+		"a=rtpmap:111 GSM-HR-08/8000\r\n"
+		"a=rtpmap:110 GSM-EFR/8000\r\n"
+		"a=ptime:20\r\n"
+		,
+		3,
+		"v=0\r\n"
+		"o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n"
+		"s=GSM Call\r\n"
+		"c=IN IP4 23.42.23.42\r\n"
+		"t=0 0\r\n"
+		"m=audio 30436 RTP/AVP 3 112 111 110\r\n"
+		"a=rtpmap:3 GSM/8000\r\n"
+		"a=rtpmap:112 AMR/8000\r\n"
+		"a=fmtp:112 octet-align=1\r\n"
+		"a=rtpmap:111 GSM-HR-08/8000\r\n"
+		"a=rtpmap:110 GSM-EFR/8000\r\n"
+		"a=ptime:20\r\n"
+	},
+	{
+		"v=0\r\n"
+		"o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n"
+		"s=GSM Call\r\n"
+		"c=IN IP4 23.42.23.42\r\n"
+		"t=0 0\r\n"
+		"m=audio 30436 RTP/AVP 112 3 111 110\r\n"
+		"a=rtpmap:112 AMR/8000\r\n"
+		"a=fmtp:112 octet-align=1\r\n"
+		"a=rtpmap:3 GSM/8000\r\n"
+		"a=rtpmap:111 GSM-HR-08/8000\r\n"
+		"a=rtpmap:110 GSM-EFR/8000\r\n"
+		"a=ptime:20\r\n"
+		,
+		111,
+		"v=0\r\n"
+		"o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n"
+		"s=GSM Call\r\n"
+		"c=IN IP4 23.42.23.42\r\n"
+		"t=0 0\r\n"
+		"m=audio 30436 RTP/AVP 111 112 3 110\r\n"
+		"a=rtpmap:111 GSM-HR-08/8000\r\n"
+		"a=rtpmap:112 AMR/8000\r\n"
+		"a=fmtp:112 octet-align=1\r\n"
+		"a=rtpmap:3 GSM/8000\r\n"
+		"a=rtpmap:110 GSM-EFR/8000\r\n"
+		"a=ptime:20\r\n"
+	},
+	{
+		"v=0\r\n"
+		"o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n"
+		"s=GSM Call\r\n"
+		"c=IN IP4 23.42.23.42\r\n"
+		"t=0 0\r\n"
+		"m=audio 30436 RTP/AVP 112 3 111 110\r\n"
+		"a=rtpmap:112 AMR/8000\r\n"
+		"a=fmtp:112 octet-align=1\r\n"
+		"a=rtpmap:3 GSM/8000\r\n"
+		"a=rtpmap:111 GSM-HR-08/8000\r\n"
+		"a=rtpmap:110 GSM-EFR/8000\r\n"
+		"a=ptime:20\r\n"
+		,
+		110,
+		"v=0\r\n"
+		"o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n"
+		"s=GSM Call\r\n"
+		"c=IN IP4 23.42.23.42\r\n"
+		"t=0 0\r\n"
+		"m=audio 30436 RTP/AVP 110 112 3 111\r\n"
+		"a=rtpmap:110 GSM-EFR/8000\r\n"
+		"a=rtpmap:112 AMR/8000\r\n"
+		"a=fmtp:112 octet-align=1\r\n"
+		"a=rtpmap:3 GSM/8000\r\n"
+		"a=rtpmap:111 GSM-HR-08/8000\r\n"
+		"a=ptime:20\r\n"
+	},
+
+};
+
+static void test_select()
+{
+	int i;
+	bool ok = true;
+	int rc;
+
+	printf("\n\n%s\n", __func__);
+
+	for (i = 0; i < ARRAY_SIZE(sdp_select_tests); i++) {
+		struct sdp_select_test_data *t = &sdp_select_tests[i];
+		struct sdp_msg sdp = {};
+		struct sdp_audio_codec *codec;
+		char buf[1024];
+		printf("\n[%d]\n", i);
+		rc = sdp_msg_from_str(&sdp, t->sdp);
+		if (rc) {
+			printf("ERROR parsing SDP: %d\n", rc);
+			break;
+		}
+		printf("SDP: %s\n", sdp_audio_codecs_name(&sdp.audio_codecs));
+		codec = sdp_audio_codec_by_payload_type(&sdp.audio_codecs, t->select_payload_type, false);
+		OSMO_ASSERT(codec);
+		printf("Select: %s\n", sdp_audio_codec_name(codec));
+
+		sdp_audio_codecs_select(&sdp.audio_codecs, codec);
+
+		printf("SDP: %s\n", sdp_audio_codecs_name(&sdp.audio_codecs));
+		sdp_msg_to_str(buf, sizeof(buf), &sdp);
+
+		if (strcmp(buf, t->expect_sdp ? : t->sdp)) {
+			int j;
+			ok = false;
+			printf("ERROR:\n");
+			dump_sdp(buf, "selection result: ");
+			dump_sdp(t->expect_sdp, "expect result: ");
+			for (j = 0; t->expect_sdp[j]; j++) {
+				if (t->expect_sdp[j] != buf[j]) {
+					printf("ERROR at position %d, at:\n", j);
+					dump_sdp(buf + j, "     mismatch: ");
+					break;
+				}
+			}
+		} else
+			printf("[%d] ok\n", i);
+	}
+
+	OSMO_ASSERT(ok);
+}
+
+int main(void)
+{
+	test_parse_and_compose();
+	test_intersect();
+	test_select();
+	return 0;
+}
diff --git a/tests/sdp_msg/sdp_msg_test.err b/tests/sdp_msg/sdp_msg_test.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/sdp_msg/sdp_msg_test.err
diff --git a/tests/sdp_msg/sdp_msg_test.ok b/tests/sdp_msg/sdp_msg_test.ok
new file mode 100644
index 0000000..a7a3d51
--- /dev/null
+++ b/tests/sdp_msg/sdp_msg_test.ok
@@ -0,0 +1,590 @@
+
+
+test_parse_and_compose
+
+[0]
+sdp input: v=0\r\n
+sdp input: o=- 5628250 5628250 IN IP4 192.168.11.121\r\n
+sdp input: s=-\r\n
+sdp input: c=IN IP4 192.168.11.121\r\n
+sdp input: t=0 0\r\n
+sdp input: m=audio 10020 RTP/AVP 18 0 2 4 8 96 97 98 100 101\r\n
+sdp input: a=rtpmap:18 G729/8000\r\n
+sdp input: a=rtpmap:0 PCMU/8000\r\n
+sdp input: a=rtpmap:2 G726-32/8000\r\n
+sdp input: a=rtpmap:4 G723/8000\r\n
+sdp input: a=rtpmap:8 PCMA/8000\r\n
+sdp input: a=rtpmap:96 G726-40/8000\r\n
+sdp input: a=rtpmap:97 G726-24/8000\r\n
+sdp input: a=rtpmap:98 G726-16/8000\r\n
+sdp input: a=rtpmap:100 NSE/8000\r\n
+sdp input: a=fmtp:100 192-193\r\n
+sdp input: a=rtpmap:101 telephone-event/8000\r\n
+sdp input: a=fmtp:101 0-15\r\n
+sdp input: a=ptime:20\r\n
+sdp input: a=sendrecv\r\n
+sdp_msg_to_str: v=0\r\n
+sdp_msg_to_str: o=OsmoMSC 0 0 IN IP4 192.168.11.121\r\n
+sdp_msg_to_str: s=GSM Call\r\n
+sdp_msg_to_str: c=IN IP4 192.168.11.121\r\n
+sdp_msg_to_str: t=0 0\r\n
+sdp_msg_to_str: m=audio 10020 RTP/AVP 18 0 2 4 8 96 97 98 100 101\r\n
+sdp_msg_to_str: a=rtpmap:18 G729/8000\r\n
+sdp_msg_to_str: a=rtpmap:0 PCMU/8000\r\n
+sdp_msg_to_str: a=rtpmap:2 G726-32/8000\r\n
+sdp_msg_to_str: a=rtpmap:4 G723/8000\r\n
+sdp_msg_to_str: a=rtpmap:8 PCMA/8000\r\n
+sdp_msg_to_str: a=rtpmap:96 G726-40/8000\r\n
+sdp_msg_to_str: a=rtpmap:97 G726-24/8000\r\n
+sdp_msg_to_str: a=rtpmap:98 G726-16/8000\r\n
+sdp_msg_to_str: a=rtpmap:100 NSE/8000\r\n
+sdp_msg_to_str: a=fmtp:100 192-193\r\n
+sdp_msg_to_str: a=rtpmap:101 telephone-event/8000\r\n
+sdp_msg_to_str: a=fmtp:101 0-15\r\n
+sdp_msg_to_str: a=ptime:20\r\n
+[0] ok
+
+[1]
+sdp input: v=0\r\n
+sdp input: o=FooBar 1565090289 1565090290 IN IP4 192.168.11.151\r\n
+sdp input: s=FooBar\r\n
+sdp input: c=IN IP4 192.168.11.151\r\n
+sdp input: t=0 0\r\n
+sdp input: m=audio 16398 RTP/AVP 98\r\n
+sdp input: a=rtpmap:98 AMR/8000\r\n
+sdp input: a=fmtp:98 octet-align=1; mode-set=4\r\n
+sdp input: a=ptime:20\r\n
+sdp input: a=rtcp:16399 IN IP4 192.168.11.151\r\n
+sdp_msg_to_str: v=0\r\n
+sdp_msg_to_str: o=OsmoMSC 0 0 IN IP4 192.168.11.151\r\n
+sdp_msg_to_str: s=GSM Call\r\n
+sdp_msg_to_str: c=IN IP4 192.168.11.151\r\n
+sdp_msg_to_str: t=0 0\r\n
+sdp_msg_to_str: m=audio 16398 RTP/AVP 98\r\n
+sdp_msg_to_str: a=rtpmap:98 AMR/8000\r\n
+sdp_msg_to_str: a=fmtp:98 octet-align=1; mode-set=4\r\n
+sdp_msg_to_str: a=ptime:20\r\n
+[1] ok
+
+[2]
+sdp input: v=0\r\n
+sdp input: o=FooBar 1565090289 1565090290 IN IP4 192.168.11.151\r\n
+sdp input: s=FooBar\r\n
+sdp input: c=IN IP4 192.168.11.140\r\n
+sdp input: t=0 0\r\n
+sdp input: m=audio 30436 RTP/AVP 18 0 4 8 101\r\n
+sdp input: a=rtpmap:18 G729/8000\r\n
+sdp input: a=rtpmap:0 PCMU/8000\r\n
+sdp input: a=rtpmap:4 G723/8000\r\n
+sdp input: a=rtpmap:8 PCMA/8000\r\n
+sdp input: a=rtpmap:101 telephone-event/8000\r\n
+sdp input: a=fmtp:101 0-15\r\n
+sdp input: a=sendrecv\r\n
+sdp input: a=rtcp:30437\r\n
+sdp input: a=ptime:20\r\n
+sdp_msg_to_str: v=0\r\n
+sdp_msg_to_str: o=OsmoMSC 0 0 IN IP4 192.168.11.140\r\n
+sdp_msg_to_str: s=GSM Call\r\n
+sdp_msg_to_str: c=IN IP4 192.168.11.140\r\n
+sdp_msg_to_str: t=0 0\r\n
+sdp_msg_to_str: m=audio 30436 RTP/AVP 18 0 4 8 101\r\n
+sdp_msg_to_str: a=rtpmap:18 G729/8000\r\n
+sdp_msg_to_str: a=rtpmap:0 PCMU/8000\r\n
+sdp_msg_to_str: a=rtpmap:4 G723/8000\r\n
+sdp_msg_to_str: a=rtpmap:8 PCMA/8000\r\n
+sdp_msg_to_str: a=rtpmap:101 telephone-event/8000\r\n
+sdp_msg_to_str: a=fmtp:101 0-15\r\n
+sdp_msg_to_str: a=ptime:20\r\n
+[2] ok
+
+
+test_intersect
+
+[0] identical codecs lead to no change
+SDP A: v=0\r\n
+SDP A: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+SDP A: s=GSM Call\r\n
+SDP A: c=IN IP4 23.42.23.42\r\n
+SDP A: t=0 0\r\n
+SDP A: m=audio 30436 RTP/AVP 112 3 111 110\r\n
+SDP A: a=rtpmap:112 AMR/8000\r\n
+SDP A: a=fmtp:112 octet-align=1\r\n
+SDP A: a=rtpmap:3 GSM/8000\r\n
+SDP A: a=rtpmap:111 GSM-HR-08/8000\r\n
+SDP A: a=rtpmap:110 GSM-EFR/8000\r\n
+SDP A: a=ptime:20\r\n
+ SDP B: c=IN IP4 5.6.7.8\r\n
+ SDP B: m=audio 12345 RTP/AVP 112 3 111 110\r\n
+ SDP B: a=rtpmap:112 AMR/8000\r\n
+ SDP B: a=fmtp:112 octet-align=1\r\n
+ SDP B: a=rtpmap:3 GSM/8000\r\n
+ SDP B: a=rtpmap:111 GSM-HR-08/8000\r\n
+ SDP B: a=rtpmap:110 GSM-EFR/8000\r\n
+parsed SDP A: v=0\r\n
+parsed SDP A: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+parsed SDP A: s=GSM Call\r\n
+parsed SDP A: c=IN IP4 23.42.23.42\r\n
+parsed SDP A: t=0 0\r\n
+parsed SDP A: m=audio 30436 RTP/AVP 112 3 111 110\r\n
+parsed SDP A: a=rtpmap:112 AMR/8000\r\n
+parsed SDP A: a=fmtp:112 octet-align=1\r\n
+parsed SDP A: a=rtpmap:3 GSM/8000\r\n
+parsed SDP A: a=rtpmap:111 GSM-HR-08/8000\r\n
+parsed SDP A: a=rtpmap:110 GSM-EFR/8000\r\n
+parsed SDP A: a=ptime:20\r\n
+parsed SDP B: v=0\r\n
+parsed SDP B: o=OsmoMSC 0 0 IN IP4 5.6.7.8\r\n
+parsed SDP B: s=GSM Call\r\n
+parsed SDP B: c=IN IP4 5.6.7.8\r\n
+parsed SDP B: t=0 0\r\n
+parsed SDP B: m=audio 12345 RTP/AVP 112 3 111 110\r\n
+parsed SDP B: a=rtpmap:112 AMR/8000\r\n
+parsed SDP B: a=fmtp:112 octet-align=1\r\n
+parsed SDP B: a=rtpmap:3 GSM/8000\r\n
+parsed SDP B: a=rtpmap:111 GSM-HR-08/8000\r\n
+parsed SDP B: a=rtpmap:110 GSM-EFR/8000\r\n
+parsed SDP B: a=ptime:20\r\n
+sdp_msg_intersection(a,b): v=0\r\n
+sdp_msg_intersection(a,b): o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+sdp_msg_intersection(a,b): s=GSM Call\r\n
+sdp_msg_intersection(a,b): c=IN IP4 23.42.23.42\r\n
+sdp_msg_intersection(a,b): t=0 0\r\n
+sdp_msg_intersection(a,b): m=audio 30436 RTP/AVP 112 3 111 110\r\n
+sdp_msg_intersection(a,b): a=rtpmap:112 AMR/8000\r\n
+sdp_msg_intersection(a,b): a=fmtp:112 octet-align=1\r\n
+sdp_msg_intersection(a,b): a=rtpmap:3 GSM/8000\r\n
+sdp_msg_intersection(a,b): a=rtpmap:111 GSM-HR-08/8000\r\n
+sdp_msg_intersection(a,b): a=rtpmap:110 GSM-EFR/8000\r\n
+sdp_msg_intersection(a,b): a=ptime:20\r\n
+[0] ok
+
+[1] identical codecs in different order also lead to no change
+SDP A: v=0\r\n
+SDP A: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+SDP A: s=GSM Call\r\n
+SDP A: c=IN IP4 23.42.23.42\r\n
+SDP A: t=0 0\r\n
+SDP A: m=audio 30436 RTP/AVP 112 3 111 110\r\n
+SDP A: a=rtpmap:112 AMR/8000\r\n
+SDP A: a=fmtp:112 octet-align=1\r\n
+SDP A: a=rtpmap:3 GSM/8000\r\n
+SDP A: a=rtpmap:111 GSM-HR-08/8000\r\n
+SDP A: a=rtpmap:110 GSM-EFR/8000\r\n
+SDP A: a=ptime:20\r\n
+ SDP B: c=IN IP4 5.6.7.8\r\n
+ SDP B: m=audio 12345 RTP/AVP 3 110 111 112\r\n
+ SDP B: a=rtpmap:3 GSM/8000\r\n
+ SDP B: a=rtpmap:110 GSM-EFR/8000\r\n
+ SDP B: a=rtpmap:111 GSM-HR-08/8000\r\n
+ SDP B: a=rtpmap:112 AMR/8000\r\n
+ SDP B: a=fmtp:112 octet-align=1\r\n
+parsed SDP A: v=0\r\n
+parsed SDP A: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+parsed SDP A: s=GSM Call\r\n
+parsed SDP A: c=IN IP4 23.42.23.42\r\n
+parsed SDP A: t=0 0\r\n
+parsed SDP A: m=audio 30436 RTP/AVP 112 3 111 110\r\n
+parsed SDP A: a=rtpmap:112 AMR/8000\r\n
+parsed SDP A: a=fmtp:112 octet-align=1\r\n
+parsed SDP A: a=rtpmap:3 GSM/8000\r\n
+parsed SDP A: a=rtpmap:111 GSM-HR-08/8000\r\n
+parsed SDP A: a=rtpmap:110 GSM-EFR/8000\r\n
+parsed SDP A: a=ptime:20\r\n
+parsed SDP B: v=0\r\n
+parsed SDP B: o=OsmoMSC 0 0 IN IP4 5.6.7.8\r\n
+parsed SDP B: s=GSM Call\r\n
+parsed SDP B: c=IN IP4 5.6.7.8\r\n
+parsed SDP B: t=0 0\r\n
+parsed SDP B: m=audio 12345 RTP/AVP 3 110 111 112\r\n
+parsed SDP B: a=rtpmap:3 GSM/8000\r\n
+parsed SDP B: a=rtpmap:110 GSM-EFR/8000\r\n
+parsed SDP B: a=rtpmap:111 GSM-HR-08/8000\r\n
+parsed SDP B: a=rtpmap:112 AMR/8000\r\n
+parsed SDP B: a=fmtp:112 octet-align=1\r\n
+parsed SDP B: a=ptime:20\r\n
+sdp_msg_intersection(a,b): v=0\r\n
+sdp_msg_intersection(a,b): o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+sdp_msg_intersection(a,b): s=GSM Call\r\n
+sdp_msg_intersection(a,b): c=IN IP4 23.42.23.42\r\n
+sdp_msg_intersection(a,b): t=0 0\r\n
+sdp_msg_intersection(a,b): m=audio 30436 RTP/AVP 112 3 111 110\r\n
+sdp_msg_intersection(a,b): a=rtpmap:112 AMR/8000\r\n
+sdp_msg_intersection(a,b): a=fmtp:112 octet-align=1\r\n
+sdp_msg_intersection(a,b): a=rtpmap:3 GSM/8000\r\n
+sdp_msg_intersection(a,b): a=rtpmap:111 GSM-HR-08/8000\r\n
+sdp_msg_intersection(a,b): a=rtpmap:110 GSM-EFR/8000\r\n
+sdp_msg_intersection(a,b): a=ptime:20\r\n
+[1] ok
+
+[2] identical codecs with mismatching payload type numbers also lead to no change
+SDP A: v=0\r\n
+SDP A: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+SDP A: s=GSM Call\r\n
+SDP A: c=IN IP4 23.42.23.42\r\n
+SDP A: t=0 0\r\n
+SDP A: m=audio 30436 RTP/AVP 112 3 111 110\r\n
+SDP A: a=rtpmap:112 AMR/8000\r\n
+SDP A: a=fmtp:112 octet-align=1\r\n
+SDP A: a=rtpmap:3 GSM/8000\r\n
+SDP A: a=rtpmap:111 GSM-HR-08/8000\r\n
+SDP A: a=rtpmap:110 GSM-EFR/8000\r\n
+SDP A: a=ptime:20\r\n
+ SDP B: c=IN IP4 5.6.7.8\r\n
+ SDP B: m=audio 12345 RTP/AVP 96 97 98 99\r\n
+ SDP B: a=rtpmap:96 GSM/8000\r\n
+ SDP B: a=rtpmap:97 GSM-EFR/8000\r\n
+ SDP B: a=rtpmap:98 GSM-HR-08/8000\r\n
+ SDP B: a=rtpmap:99 AMR/8000\r\n
+ SDP B: a=fmtp:99 octet-align=1\r\n
+parsed SDP A: v=0\r\n
+parsed SDP A: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+parsed SDP A: s=GSM Call\r\n
+parsed SDP A: c=IN IP4 23.42.23.42\r\n
+parsed SDP A: t=0 0\r\n
+parsed SDP A: m=audio 30436 RTP/AVP 112 3 111 110\r\n
+parsed SDP A: a=rtpmap:112 AMR/8000\r\n
+parsed SDP A: a=fmtp:112 octet-align=1\r\n
+parsed SDP A: a=rtpmap:3 GSM/8000\r\n
+parsed SDP A: a=rtpmap:111 GSM-HR-08/8000\r\n
+parsed SDP A: a=rtpmap:110 GSM-EFR/8000\r\n
+parsed SDP A: a=ptime:20\r\n
+parsed SDP B: v=0\r\n
+parsed SDP B: o=OsmoMSC 0 0 IN IP4 5.6.7.8\r\n
+parsed SDP B: s=GSM Call\r\n
+parsed SDP B: c=IN IP4 5.6.7.8\r\n
+parsed SDP B: t=0 0\r\n
+parsed SDP B: m=audio 12345 RTP/AVP 96 97 98 99\r\n
+parsed SDP B: a=rtpmap:96 GSM/8000\r\n
+parsed SDP B: a=rtpmap:97 GSM-EFR/8000\r\n
+parsed SDP B: a=rtpmap:98 GSM-HR-08/8000\r\n
+parsed SDP B: a=rtpmap:99 AMR/8000\r\n
+parsed SDP B: a=fmtp:99 octet-align=1\r\n
+parsed SDP B: a=ptime:20\r\n
+sdp_msg_intersection(a,b): v=0\r\n
+sdp_msg_intersection(a,b): o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+sdp_msg_intersection(a,b): s=GSM Call\r\n
+sdp_msg_intersection(a,b): c=IN IP4 23.42.23.42\r\n
+sdp_msg_intersection(a,b): t=0 0\r\n
+sdp_msg_intersection(a,b): m=audio 30436 RTP/AVP 112 3 111 110\r\n
+sdp_msg_intersection(a,b): a=rtpmap:112 AMR/8000\r\n
+sdp_msg_intersection(a,b): a=fmtp:112 octet-align=1\r\n
+sdp_msg_intersection(a,b): a=rtpmap:3 GSM/8000\r\n
+sdp_msg_intersection(a,b): a=rtpmap:111 GSM-HR-08/8000\r\n
+sdp_msg_intersection(a,b): a=rtpmap:110 GSM-EFR/8000\r\n
+sdp_msg_intersection(a,b): a=ptime:20\r\n
+[2] ok
+
+[3] identical codecs plus some extra codecs also lead to no change
+SDP A: v=0\r\n
+SDP A: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+SDP A: s=GSM Call\r\n
+SDP A: c=IN IP4 23.42.23.42\r\n
+SDP A: t=0 0\r\n
+SDP A: m=audio 30436 RTP/AVP 112 3 111 110\r\n
+SDP A: a=rtpmap:112 AMR/8000\r\n
+SDP A: a=fmtp:112 octet-align=1\r\n
+SDP A: a=rtpmap:3 GSM/8000\r\n
+SDP A: a=rtpmap:111 GSM-HR-08/8000\r\n
+SDP A: a=rtpmap:110 GSM-EFR/8000\r\n
+SDP A: a=ptime:20\r\n
+ SDP B: c=IN IP4 5.6.7.8\r\n
+ SDP B: m=audio 12345 RTP/AVP 8 0 96 97 98 99\r\n
+ SDP B: a=rtpmap:8 PCMA/8000\r\n
+ SDP B: a=rtpmap:0 PCMU/8000\r\n
+ SDP B: a=rtpmap:96 GSM/8000\r\n
+ SDP B: a=rtpmap:97 GSM-EFR/8000\r\n
+ SDP B: a=rtpmap:98 GSM-HR-08/8000\r\n
+ SDP B: a=rtpmap:99 AMR/8000\r\n
+ SDP B: a=fmtp:99 octet-align=1\r\n
+parsed SDP A: v=0\r\n
+parsed SDP A: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+parsed SDP A: s=GSM Call\r\n
+parsed SDP A: c=IN IP4 23.42.23.42\r\n
+parsed SDP A: t=0 0\r\n
+parsed SDP A: m=audio 30436 RTP/AVP 112 3 111 110\r\n
+parsed SDP A: a=rtpmap:112 AMR/8000\r\n
+parsed SDP A: a=fmtp:112 octet-align=1\r\n
+parsed SDP A: a=rtpmap:3 GSM/8000\r\n
+parsed SDP A: a=rtpmap:111 GSM-HR-08/8000\r\n
+parsed SDP A: a=rtpmap:110 GSM-EFR/8000\r\n
+parsed SDP A: a=ptime:20\r\n
+parsed SDP B: v=0\r\n
+parsed SDP B: o=OsmoMSC 0 0 IN IP4 5.6.7.8\r\n
+parsed SDP B: s=GSM Call\r\n
+parsed SDP B: c=IN IP4 5.6.7.8\r\n
+parsed SDP B: t=0 0\r\n
+parsed SDP B: m=audio 12345 RTP/AVP 8 0 96 97 98 99\r\n
+parsed SDP B: a=rtpmap:8 PCMA/8000\r\n
+parsed SDP B: a=rtpmap:0 PCMU/8000\r\n
+parsed SDP B: a=rtpmap:96 GSM/8000\r\n
+parsed SDP B: a=rtpmap:97 GSM-EFR/8000\r\n
+parsed SDP B: a=rtpmap:98 GSM-HR-08/8000\r\n
+parsed SDP B: a=rtpmap:99 AMR/8000\r\n
+parsed SDP B: a=fmtp:99 octet-align=1\r\n
+parsed SDP B: a=ptime:20\r\n
+sdp_msg_intersection(a,b): v=0\r\n
+sdp_msg_intersection(a,b): o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+sdp_msg_intersection(a,b): s=GSM Call\r\n
+sdp_msg_intersection(a,b): c=IN IP4 23.42.23.42\r\n
+sdp_msg_intersection(a,b): t=0 0\r\n
+sdp_msg_intersection(a,b): m=audio 30436 RTP/AVP 112 3 111 110\r\n
+sdp_msg_intersection(a,b): a=rtpmap:112 AMR/8000\r\n
+sdp_msg_intersection(a,b): a=fmtp:112 octet-align=1\r\n
+sdp_msg_intersection(a,b): a=rtpmap:3 GSM/8000\r\n
+sdp_msg_intersection(a,b): a=rtpmap:111 GSM-HR-08/8000\r\n
+sdp_msg_intersection(a,b): a=rtpmap:110 GSM-EFR/8000\r\n
+sdp_msg_intersection(a,b): a=ptime:20\r\n
+[3] ok
+
+[4] some codecs removed
+SDP A: v=0\r\n
+SDP A: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+SDP A: s=GSM Call\r\n
+SDP A: c=IN IP4 23.42.23.42\r\n
+SDP A: t=0 0\r\n
+SDP A: m=audio 30436 RTP/AVP 112 3 111 110\r\n
+SDP A: a=rtpmap:112 AMR/8000\r\n
+SDP A: a=fmtp:112 octet-align=1\r\n
+SDP A: a=rtpmap:3 GSM/8000\r\n
+SDP A: a=rtpmap:111 GSM-HR-08/8000\r\n
+SDP A: a=rtpmap:110 GSM-EFR/8000\r\n
+SDP A: a=ptime:20\r\n
+ SDP B: v=0\r\n
+ SDP B: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+ SDP B: s=GSM Call\r\n
+ SDP B: c=IN IP4 23.42.23.42\r\n
+ SDP B: t=0 0\r\n
+ SDP B: m=audio 30436 RTP/AVP 112 110\r\n
+ SDP B: a=rtpmap:112 AMR/8000\r\n
+ SDP B: a=fmtp:112 octet-align=1\r\n
+ SDP B: a=rtpmap:110 GSM-EFR/8000\r\n
+ SDP B: a=ptime:20\r\n
+parsed SDP A: v=0\r\n
+parsed SDP A: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+parsed SDP A: s=GSM Call\r\n
+parsed SDP A: c=IN IP4 23.42.23.42\r\n
+parsed SDP A: t=0 0\r\n
+parsed SDP A: m=audio 30436 RTP/AVP 112 3 111 110\r\n
+parsed SDP A: a=rtpmap:112 AMR/8000\r\n
+parsed SDP A: a=fmtp:112 octet-align=1\r\n
+parsed SDP A: a=rtpmap:3 GSM/8000\r\n
+parsed SDP A: a=rtpmap:111 GSM-HR-08/8000\r\n
+parsed SDP A: a=rtpmap:110 GSM-EFR/8000\r\n
+parsed SDP A: a=ptime:20\r\n
+parsed SDP B: v=0\r\n
+parsed SDP B: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+parsed SDP B: s=GSM Call\r\n
+parsed SDP B: c=IN IP4 23.42.23.42\r\n
+parsed SDP B: t=0 0\r\n
+parsed SDP B: m=audio 30436 RTP/AVP 112 110\r\n
+parsed SDP B: a=rtpmap:112 AMR/8000\r\n
+parsed SDP B: a=fmtp:112 octet-align=1\r\n
+parsed SDP B: a=rtpmap:110 GSM-EFR/8000\r\n
+parsed SDP B: a=ptime:20\r\n
+sdp_msg_intersection(a,b): v=0\r\n
+sdp_msg_intersection(a,b): o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+sdp_msg_intersection(a,b): s=GSM Call\r\n
+sdp_msg_intersection(a,b): c=IN IP4 23.42.23.42\r\n
+sdp_msg_intersection(a,b): t=0 0\r\n
+sdp_msg_intersection(a,b): m=audio 30436 RTP/AVP 112 110\r\n
+sdp_msg_intersection(a,b): a=rtpmap:112 AMR/8000\r\n
+sdp_msg_intersection(a,b): a=fmtp:112 octet-align=1\r\n
+sdp_msg_intersection(a,b): a=rtpmap:110 GSM-EFR/8000\r\n
+sdp_msg_intersection(a,b): a=ptime:20\r\n
+[4] ok
+
+[5] other codecs removed
+SDP A: v=0\r\n
+SDP A: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+SDP A: s=GSM Call\r\n
+SDP A: c=IN IP4 23.42.23.42\r\n
+SDP A: t=0 0\r\n
+SDP A: m=audio 30436 RTP/AVP 112 3 111 110\r\n
+SDP A: a=rtpmap:112 AMR/8000\r\n
+SDP A: a=fmtp:112 octet-align=1\r\n
+SDP A: a=rtpmap:3 GSM/8000\r\n
+SDP A: a=rtpmap:111 GSM-HR-08/8000\r\n
+SDP A: a=rtpmap:110 GSM-EFR/8000\r\n
+SDP A: a=ptime:20\r\n
+ SDP B: v=0\r\n
+ SDP B: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+ SDP B: s=GSM Call\r\n
+ SDP B: c=IN IP4 23.42.23.42\r\n
+ SDP B: t=0 0\r\n
+ SDP B: m=audio 30436 RTP/AVP 3 111\r\n
+ SDP B: a=rtpmap:3 GSM/8000\r\n
+ SDP B: a=rtpmap:111 GSM-HR-08/8000\r\n
+ SDP B: a=ptime:20\r\n
+parsed SDP A: v=0\r\n
+parsed SDP A: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+parsed SDP A: s=GSM Call\r\n
+parsed SDP A: c=IN IP4 23.42.23.42\r\n
+parsed SDP A: t=0 0\r\n
+parsed SDP A: m=audio 30436 RTP/AVP 112 3 111 110\r\n
+parsed SDP A: a=rtpmap:112 AMR/8000\r\n
+parsed SDP A: a=fmtp:112 octet-align=1\r\n
+parsed SDP A: a=rtpmap:3 GSM/8000\r\n
+parsed SDP A: a=rtpmap:111 GSM-HR-08/8000\r\n
+parsed SDP A: a=rtpmap:110 GSM-EFR/8000\r\n
+parsed SDP A: a=ptime:20\r\n
+parsed SDP B: v=0\r\n
+parsed SDP B: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+parsed SDP B: s=GSM Call\r\n
+parsed SDP B: c=IN IP4 23.42.23.42\r\n
+parsed SDP B: t=0 0\r\n
+parsed SDP B: m=audio 30436 RTP/AVP 3 111\r\n
+parsed SDP B: a=rtpmap:3 GSM/8000\r\n
+parsed SDP B: a=rtpmap:111 GSM-HR-08/8000\r\n
+parsed SDP B: a=ptime:20\r\n
+sdp_msg_intersection(a,b): v=0\r\n
+sdp_msg_intersection(a,b): o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+sdp_msg_intersection(a,b): s=GSM Call\r\n
+sdp_msg_intersection(a,b): c=IN IP4 23.42.23.42\r\n
+sdp_msg_intersection(a,b): t=0 0\r\n
+sdp_msg_intersection(a,b): m=audio 30436 RTP/AVP 3 111\r\n
+sdp_msg_intersection(a,b): a=rtpmap:3 GSM/8000\r\n
+sdp_msg_intersection(a,b): a=rtpmap:111 GSM-HR-08/8000\r\n
+sdp_msg_intersection(a,b): a=ptime:20\r\n
+[5] ok
+
+[6] all codecs removed
+SDP A: v=0\r\n
+SDP A: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+SDP A: s=GSM Call\r\n
+SDP A: c=IN IP4 23.42.23.42\r\n
+SDP A: t=0 0\r\n
+SDP A: m=audio 30436 RTP/AVP 112 3 111 110\r\n
+SDP A: a=rtpmap:112 AMR/8000\r\n
+SDP A: a=fmtp:112 octet-align=1\r\n
+SDP A: a=rtpmap:3 GSM/8000\r\n
+SDP A: a=rtpmap:111 GSM-HR-08/8000\r\n
+SDP A: a=rtpmap:110 GSM-EFR/8000\r\n
+SDP A: a=ptime:20\r\n
+ SDP B: s=empty
+parsed SDP A: v=0\r\n
+parsed SDP A: o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+parsed SDP A: s=GSM Call\r\n
+parsed SDP A: c=IN IP4 23.42.23.42\r\n
+parsed SDP A: t=0 0\r\n
+parsed SDP A: m=audio 30436 RTP/AVP 112 3 111 110\r\n
+parsed SDP A: a=rtpmap:112 AMR/8000\r\n
+parsed SDP A: a=fmtp:112 octet-align=1\r\n
+parsed SDP A: a=rtpmap:3 GSM/8000\r\n
+parsed SDP A: a=rtpmap:111 GSM-HR-08/8000\r\n
+parsed SDP A: a=rtpmap:110 GSM-EFR/8000\r\n
+parsed SDP A: a=ptime:20\r\n
+parsed SDP B: v=0\r\n
+parsed SDP B: o=OsmoMSC 0 0 IN IP4 0.0.0.0\r\n
+parsed SDP B: s=GSM Call\r\n
+parsed SDP B: c=IN IP4 0.0.0.0\r\n
+parsed SDP B: t=0 0\r\n
+parsed SDP B: m=audio 0 RTP/AVP\r\n
+parsed SDP B: a=ptime:20\r\n
+sdp_msg_intersection(a,b): v=0\r\n
+sdp_msg_intersection(a,b): o=OsmoMSC 0 0 IN IP4 23.42.23.42\r\n
+sdp_msg_intersection(a,b): s=GSM Call\r\n
+sdp_msg_intersection(a,b): c=IN IP4 23.42.23.42\r\n
+sdp_msg_intersection(a,b): t=0 0\r\n
+sdp_msg_intersection(a,b): m=audio 30436 RTP/AVP\r\n
+sdp_msg_intersection(a,b): a=ptime:20\r\n
+[6] ok
+
+[7] some real world test case
+SDP A: v=0\r\n
+SDP A: o=OsmoMSC 0 0 IN IP4 0.0.0.0\r\n
+SDP A: s=GSM Call\r\n
+SDP A: c=IN IP4 0.0.0.0\r\n
+SDP A: t=0 0\r\n
+SDP A: m=audio 0 RTP/AVP 112 113 110 3 111\r\n
+SDP A: a=rtpmap:112 AMR/8000\r\n
+SDP A: a=fmtp:112 octet-align=1;mode-set=0,1,2,3\r\n
+SDP A: a=rtpmap:113 AMR-WB/8000\r\n
+SDP A: a=fmtp:113 octet-align=1\r\n
+SDP A: a=rtpmap:110 GSM-EFR/8000\r\n
+SDP A: a=rtpmap:3 GSM/8000\r\n
+SDP A: a=rtpmap:111 GSM-HR-08/8000\r\n
+SDP A: a=ptime:20\r\n
+ SDP B: v=0\r\n
+ SDP B: o=OsmoMSC 0 0 IN IP4 0.0.0.0\r\n
+ SDP B: s=GSM Call\r\n
+ SDP B: c=IN IP4 0.0.0.0\r\n
+ SDP B: t=0 0\r\n
+ SDP B: m=audio 0 RTP/AVP 112 113 110 3 111\r\n
+ SDP B: a=rtpmap:112 AMR/8000\r\n
+ SDP B: a=fmtp:112 octet-align=1;mode-set=0,1,2,3\r\n
+ SDP B: a=rtpmap:113 AMR-WB/8000\r\n
+ SDP B: a=fmtp:113 octet-align=1\r\n
+ SDP B: a=rtpmap:110 GSM-EFR/8000\r\n
+ SDP B: a=rtpmap:3 GSM/8000\r\n
+ SDP B: a=rtpmap:111 GSM-HR-08/8000\r\n
+ SDP B: a=ptime:20\r\n
+parsed SDP A: v=0\r\n
+parsed SDP A: o=OsmoMSC 0 0 IN IP4 0.0.0.0\r\n
+parsed SDP A: s=GSM Call\r\n
+parsed SDP A: c=IN IP4 0.0.0.0\r\n
+parsed SDP A: t=0 0\r\n
+parsed SDP A: m=audio 0 RTP/AVP 112 113 110 3 111\r\n
+parsed SDP A: a=rtpmap:112 AMR/8000\r\n
+parsed SDP A: a=fmtp:112 octet-align=1;mode-set=0,1,2,3\r\n
+parsed SDP A: a=rtpmap:113 AMR-WB/8000\r\n
+parsed SDP A: a=fmtp:113 octet-align=1\r\n
+parsed SDP A: a=rtpmap:110 GSM-EFR/8000\r\n
+parsed SDP A: a=rtpmap:3 GSM/8000\r\n
+parsed SDP A: a=rtpmap:111 GSM-HR-08/8000\r\n
+parsed SDP A: a=ptime:20\r\n
+parsed SDP B: v=0\r\n
+parsed SDP B: o=OsmoMSC 0 0 IN IP4 0.0.0.0\r\n
+parsed SDP B: s=GSM Call\r\n
+parsed SDP B: c=IN IP4 0.0.0.0\r\n
+parsed SDP B: t=0 0\r\n
+parsed SDP B: m=audio 0 RTP/AVP 112 113 110 3 111\r\n
+parsed SDP B: a=rtpmap:112 AMR/8000\r\n
+parsed SDP B: a=fmtp:112 octet-align=1;mode-set=0,1,2,3\r\n
+parsed SDP B: a=rtpmap:113 AMR-WB/8000\r\n
+parsed SDP B: a=fmtp:113 octet-align=1\r\n
+parsed SDP B: a=rtpmap:110 GSM-EFR/8000\r\n
+parsed SDP B: a=rtpmap:3 GSM/8000\r\n
+parsed SDP B: a=rtpmap:111 GSM-HR-08/8000\r\n
+parsed SDP B: a=ptime:20\r\n
+sdp_msg_intersection(a,b): v=0\r\n
+sdp_msg_intersection(a,b): o=OsmoMSC 0 0 IN IP4 0.0.0.0\r\n
+sdp_msg_intersection(a,b): s=GSM Call\r\n
+sdp_msg_intersection(a,b): c=IN IP4 0.0.0.0\r\n
+sdp_msg_intersection(a,b): t=0 0\r\n
+sdp_msg_intersection(a,b): m=audio 0 RTP/AVP 112 113 110 3 111\r\n
+sdp_msg_intersection(a,b): a=rtpmap:112 AMR/8000\r\n
+sdp_msg_intersection(a,b): a=fmtp:112 octet-align=1;mode-set=0,1,2,3\r\n
+sdp_msg_intersection(a,b): a=rtpmap:113 AMR-WB/8000\r\n
+sdp_msg_intersection(a,b): a=fmtp:113 octet-align=1\r\n
+sdp_msg_intersection(a,b): a=rtpmap:110 GSM-EFR/8000\r\n
+sdp_msg_intersection(a,b): a=rtpmap:3 GSM/8000\r\n
+sdp_msg_intersection(a,b): a=rtpmap:111 GSM-HR-08/8000\r\n
+sdp_msg_intersection(a,b): a=ptime:20\r\n
+[7] ok
+
+
+test_select
+
+[0]
+SDP: AMR:octet-align=1,GSM,GSM-HR-08,GSM-EFR
+Select: AMR:octet-align=1
+SDP: AMR:octet-align=1,GSM,GSM-HR-08,GSM-EFR
+[0] ok
+
+[1]
+SDP: AMR:octet-align=1,GSM,GSM-HR-08,GSM-EFR
+Select: GSM
+SDP: GSM,AMR:octet-align=1,GSM-HR-08,GSM-EFR
+[1] ok
+
+[2]
+SDP: AMR:octet-align=1,GSM,GSM-HR-08,GSM-EFR
+Select: GSM-HR-08
+SDP: GSM-HR-08,AMR:octet-align=1,GSM,GSM-EFR
+[2] ok
+
+[3]
+SDP: AMR:octet-align=1,GSM,GSM-HR-08,GSM-EFR
+Select: GSM-EFR
+SDP: GSM-EFR,AMR:octet-align=1,GSM,GSM-HR-08
+[3] ok
diff --git a/tests/testsuite.at b/tests/testsuite.at
index c0788b9..946d0db 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -107,3 +107,10 @@
 cat $abs_srcdir/msc_vlr/msc_vlr_test_ss.err > experr
 AT_CHECK([$abs_top_builddir/tests/msc_vlr/msc_vlr_test_ss], [], [expout], [experr])
 AT_CLEANUP
+
+AT_SETUP([sdp_msg_test])
+AT_KEYWORDS([sdp_msg_test])
+cat $abs_srcdir/sdp_msg/sdp_msg_test.ok > expout
+cat $abs_srcdir/sdp_msg/sdp_msg_test.err > experr
+AT_CHECK([$abs_top_builddir/tests/sdp_msg/sdp_msg_test], [], [expout], [experr])
+AT_CLEANUP