From b8f52353f367ae8b8da26503a7fe0d4a78d07536 Mon Sep 17 00:00:00 2001 From: Michael Jerris Date: Thu, 12 Feb 2009 21:28:58 +0000 Subject: [PATCH] Thu Feb 12 15:14:07 CST 2009 Pekka Pessi * soa: make better effort in keeping addresses (c= and o= lines) in user SDP Ignore-this: a6da9ed249dba3309e3dbbbdb4262082 The address selection logic now prefers (unicast) addresses in already present in SDP, if they get returned by su_getlocalinfo(). The process currently tries to avoid link-local addresses. git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@11962 d0543943-73ff-0310-b7d9-9358b9ac24b2 --- libs/sofia-sip/.update | 2 +- .../sofia-sip/libsofia-sip-ua/soa/Makefile.am | 6 +- libs/sofia-sip/libsofia-sip-ua/soa/soa.c | 603 ++++++++++++++---- .../libsofia-sip-ua/soa/soa_static.c | 67 +- .../soa/sofia-sip/soa_session.h | 16 +- libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c | 557 ++++++++++++++++ 6 files changed, 1105 insertions(+), 146 deletions(-) diff --git a/libs/sofia-sip/.update b/libs/sofia-sip/.update index 55882da542..c7d6d94272 100644 --- a/libs/sofia-sip/.update +++ b/libs/sofia-sip/.update @@ -1 +1 @@ -Thu Feb 12 15:25:06 CST 2009 +Thu Feb 12 15:28:20 CST 2009 diff --git a/libs/sofia-sip/libsofia-sip-ua/soa/Makefile.am b/libs/sofia-sip/libsofia-sip-ua/soa/Makefile.am index ddd11b5b9d..595cde538a 100644 --- a/libs/sofia-sip/libsofia-sip-ua/soa/Makefile.am +++ b/libs/sofia-sip/libsofia-sip-ua/soa/Makefile.am @@ -14,7 +14,8 @@ INCLUDES = -I$(srcdir)/../sdp -I../sdp \ -I$(srcdir)/../url -I../url \ -I$(srcdir)/../ipt -I../ipt \ -I$(srcdir)/../bnf -I../bnf \ - -I$(srcdir)/../su -I../su + -I$(srcdir)/../su -I../su \ + -I$(top_srcdir)/s2check # ---------------------------------------------------------------------- # Build targets @@ -39,7 +40,8 @@ libsoa_la_SOURCES = soa.c soa_static.c \ COVERAGE_INPUT = $(libsoa_la_SOURCES) $(include_sofia_HEADERS) -LDADD = libsoa.la \ +test_soa_LDADD = $(top_builddir)/s2check/libs2.a \ + libsoa.la \ ../sip/libsip.la \ ../msg/libmsg.la \ ../url/liburl.la \ diff --git a/libs/sofia-sip/libsofia-sip-ua/soa/soa.c b/libs/sofia-sip/libsofia-sip-ua/soa/soa.c index 77f1ab6a46..49be1faa7d 100644 --- a/libs/sofia-sip/libsofia-sip-ua/soa/soa.c +++ b/libs/sofia-sip/libsofia-sip-ua/soa/soa.c @@ -48,6 +48,7 @@ #include "sofia-sip/soa_add.h" #include +#include #include #include #include @@ -2166,15 +2167,36 @@ void soa_description_free(soa_session_t *ss, su_free(ss->ss_home, tbf4); } - /** Initialize SDP o= line */ int soa_init_sdp_origin(soa_session_t *ss, sdp_origin_t *o, char buffer[64]) { - sdp_connection_t *c; + return soa_init_sdp_origin_with_session(ss, o, buffer, NULL); +} - if (ss == NULL || o == NULL) - return su_seterrno(EFAULT), -1; + +/** Check if c= has valid address + */ +int +soa_check_sdp_connection(sdp_connection_t const *c) +{ + return c != NULL && + c->c_nettype &&c->c_address && + strcmp(c->c_address, "") && + strcmp(c->c_address, "0.0.0.0") && + strcmp(c->c_address, "::"); +} + + +/** Initialize SDP o= line with values from @a sdp session. */ +int +soa_init_sdp_origin_with_session(soa_session_t *ss, + sdp_origin_t *o, + char buffer[64], + sdp_session_t const *sdp) +{ + if (ss == NULL || o == NULL || buffer == NULL) + return su_seterrno(EFAULT); assert(o->o_address); @@ -2189,49 +2211,13 @@ soa_init_sdp_origin(soa_session_t *ss, sdp_origin_t *o, char buffer[64]) su_randmem(&o->o_version, sizeof o->o_version); o->o_version &= ((unsigned longlong)1 << 63) - 1; - c = o->o_address; - - if (!c->c_nettype || - !c->c_address || - strcmp(c->c_address, "") == 0 || - strcmp(c->c_address, "0.0.0.0") == 0 || - strcmp(c->c_address, "::") == 0 || - !host_is_local(c->c_address)) { - return soa_init_sdp_connection(ss, c, buffer); - } + if (!soa_check_sdp_connection(o->o_address) || + host_is_local(o->o_address->c_address)) + return soa_init_sdp_connection_with_session(ss, o->o_address, buffer, sdp); return 0; } -/** Search for an local address item from string provided by user */ -static -su_localinfo_t *li_in_list(su_localinfo_t *li0, char const **llist) -{ - char const *list = *llist; - size_t n; - - if (!list) - return NULL; - - while ((n = strcspn(list, ", "))) { - su_localinfo_t *li; - - for (li = li0; li; li = li->li_next) { - if (su_casenmatch(li->li_canonname, list, n) && - li->li_canonname[n] == '\0') - break; - } - - list += n; while (list[0] == ' ' || list[0] == ',') list++; - *llist = list; - - if (li) - return li; - } - - return NULL; -} - /** Obtain a local address for SDP connection structure */ int @@ -2239,12 +2225,35 @@ soa_init_sdp_connection(soa_session_t *ss, sdp_connection_t *c, char buffer[64]) { - su_localinfo_t *res, hints[1] = {{ LI_CANONNAME | LI_NUMERIC }}; - su_localinfo_t *li, *li4, *li6; - char const *address; - int ip4, ip6, error; + return soa_init_sdp_connection_with_session(ss, c, buffer, NULL); +} - if (ss == NULL || c == NULL) +static su_localinfo_t const *best_listed_address_in_localinfo( + su_localinfo_t const *res, char const *address, int ip4, int ip6); +static sdp_connection_t const *best_listed_address_in_session( + sdp_session_t const *sdp, char const *address0, int ip4, int ip6); +static su_localinfo_t const *best_listed_address( + su_localinfo_t *li0, char const *address, int ip4, int ip6); + + +/** Obtain a local address for SDP connection structure. + * + * Prefer an address already found in @a sdp. + */ +int +soa_init_sdp_connection_with_session(soa_session_t *ss, + sdp_connection_t *c, + char buffer[64], + sdp_session_t const *sdp) +{ + su_localinfo_t *res, hints[1] = {{ LI_CANONNAME | LI_NUMERIC }}, li0[1]; + su_localinfo_t const *li, *li4, *li6; + char const *address; + char const *source = NULL; + int ip4, ip6, error; + char abuffer[64]; /* getting value from ss_address */ + + if (ss == NULL || c == NULL || buffer == NULL) return su_seterrno(EFAULT), -1; address = ss->ss_address; @@ -2268,32 +2277,15 @@ soa_init_sdp_connection(soa_session_t *ss, c->c_address = memcpy(buffer, address + 1, len - 1); buffer[len - 1] = '\0'; } + + SU_DEBUG_5(("%s: using SOATAG_ADDRESS(\"%s\")\n", __func__, c->c_address)); + return 0; } /* XXX - using LI_SCOPE_LINK requires some tweaking */ hints->li_scope = LI_SCOPE_GLOBAL | LI_SCOPE_SITE /* | LI_SCOPE_LINK */; - switch (ss->ss_af) { - case SOA_AF_IP4_ONLY: - hints->li_family = AF_INET, ip4 = 1, ip6 = 0; - break; - -#if HAVE_SIN6 - case SOA_AF_IP6_ONLY: - hints->li_family = AF_INET6, ip6 = 1, ip4 = 0; - break; - case SOA_AF_IP4_IP6: - ip4 = 2, ip6 = 1; - break; - case SOA_AF_IP6_IP4: - ip4 = 1, ip6 = 2; - break; -#endif - default: - ip4 = ip6 = 1; - } - for (res = NULL; res == NULL;) { if ((error = su_getlocalinfo(hints, &res)) < 0 && error != ELI_NOADDRESS) { @@ -2306,62 +2298,270 @@ soa_init_sdp_connection(soa_session_t *ss, hints->li_scope |= LI_SCOPE_HOST; } - if (!(ip4 & ip6 && c->c_nettype == sdp_net_in)) - /* Use ss_af preference */; - else if (c->c_addrtype == sdp_addr_ip4) - ip4 = 2, ip6 = 1; - else if (c->c_addrtype == sdp_addr_ip6) - ip6 = 2, ip4 = 1; - - if (address) - SU_DEBUG_3(("%s: searching for %s from list \"%s\"\n", - __func__, ip6 && !ip4 ? "IP6 " : !ip6 && ip4 ? "IP4 " : "", - address)); - - li = res, li4 = NULL, li6 = NULL; - - for (;;) { - if (address) - li = li_in_list(li, &address); - - if (!li) - break; -#if HAVE_SIN6 - else if (li->li_family == AF_INET6) { - if (ip6 >= ip4) - break; - else if (!li6) - li6 = li; /* Best IP6 address */ - } -#endif - else if (li->li_family == AF_INET) { - if (ip4 > ip6) - break; - else if (!li4) - li4 = li; /* Best IP4 address */ - } - - if (!address) - li = li->li_next; + if (c->c_nettype != sdp_net_in || + (c->c_addrtype != sdp_addr_ip4 && c->c_addrtype != sdp_addr_ip6)) { + c->c_nettype = sdp_net_in, c->c_addrtype = 0; + c->c_address = strcpy(buffer, ""); } - if (li == NULL) - li = li4; - if (li == NULL) - li = li6; + switch (ss->ss_af) { + case SOA_AF_IP4_ONLY: + ip4 = 1, ip6 = 0; + break; + case SOA_AF_IP6_ONLY: + ip6 = 1, ip4 = 0; + break; + case SOA_AF_IP4_IP6: + ip4 = 2, ip6 = 1; + break; + case SOA_AF_IP6_IP4: + ip4 = 1, ip6 = 2; + break; + default: + ip4 = ip6 = 1; + } - if (li == NULL) - ; - else if (li->li_family == AF_INET) - c->c_nettype = sdp_net_in, c->c_addrtype = sdp_addr_ip4; -#if HAVE_SIN6 - else if (li->li_family == AF_INET6) - c->c_nettype = sdp_net_in, c->c_addrtype = sdp_addr_ip6; + if (ip4 && ip6) { + /* Prefer address family already used in session, if any */ + sdp_addrtype_e addrtype = 0; + char const *because = "error"; + + if (sdp && sdp->sdp_connection && + sdp->sdp_connection->c_nettype == sdp_net_in) { + addrtype = sdp->sdp_connection->c_addrtype; + because = "an existing c= line"; + } + else if (sdp) { + int mip4 = 0, mip6 = 0; + sdp_media_t const *m; + + for (m = sdp->sdp_media; m; m = m->m_next) { + sdp_connection_t const *mc; + + if (m->m_rejected) + continue; + + for (mc = m->m_connections; mc; mc = mc->c_next) { + if (mc->c_nettype == sdp_net_in) { + if (mc->c_addrtype == sdp_addr_ip4) + mip4++; + else if (mc->c_addrtype == sdp_addr_ip6) + mip6++; + } + } + } + + if (mip4 && mip6) + /* Mixed. */; + else if (mip4) + addrtype = sdp_addr_ip4, because = "an existing c= line under m="; + else if (mip6) + addrtype = sdp_addr_ip6, because = "an existing c= line under m="; + } + + if (addrtype == 0) + addrtype = c->c_addrtype, because = "the user sdp"; + + if (addrtype == sdp_addr_ip4) { + if (ip6 >= ip4) + SU_DEBUG_5(("%s: prefer %s because of %s\n", __func__, "IP4", because)); + ip4 = 2, ip6 = 1; + } + else if (addrtype == sdp_addr_ip6) { + if (ip4 >= ip6) + SU_DEBUG_5(("%s: prefer %s because of %s\n", __func__, "IP4", because)); + ip6 = 2, ip4 = 1; + } + } + + li = NULL, li4 = NULL, li6 = NULL; + + if (li == NULL && ss->ss_address) { + li = best_listed_address_in_localinfo(res, ss->ss_address, ip4, ip6); + if (li) + source = "local address from SOATAG_ADDRESS() list"; + } + + if (li == NULL && ss->ss_address && sdp) { + sdp_connection_t const *c; + c = best_listed_address_in_session(sdp, ss->ss_address, ip4, ip6); + if (c) { + li = memset(li0, 0, sizeof li0); + if (c->c_addrtype == sdp_addr_ip4) + li0->li_family = AF_INET; +#if SU_HAVE_IN6 + else + li0->li_family = AF_INET6; #endif + li0->li_canonname = (char *)c->c_address; + source = "address from SOATAG_ADDRESS() list already in session"; + } + } + + if (li == NULL && ss->ss_address) { + memset(li0, 0, sizeof li0); + li0->li_canonname = abuffer; + li = best_listed_address(li0, ss->ss_address, ip4, ip6); + if (li) + source = "address from SOATAG_ADDRESS() list"; + } + + if (li == NULL) { + for (li = res; li; li = li->li_next) { + if (su_casematch(li->li_canonname, c->c_address)) + break; + } + if (li) + source = "the proposed local address"; + } + + /* Check if session-level c= address is local */ + if (li == NULL && sdp && sdp->sdp_connection) { + for (li = res; li; li = li->li_next) { + if (!su_casematch(li->li_canonname, sdp->sdp_connection->c_address)) + continue; +#if HAVE_SIN6 + if (li->li_family == AF_INET6) { + if (ip6 >= ip4) + break; + else if (!li6) + li6 = li; /* Best IP6 address */ + } +#endif + else if (li->li_family == AF_INET) { + if (ip4 >= ip6) + break; + else if (!li4) + li4 = li; /* Best IP4 address */ + } + } + + if (li == NULL && ip4) + li = li4; + if (li == NULL && ip6) + li = li6; + if (li) + source = "an existing session-level c= line"; + } + + /* Check for best local media-level c= address */ + if (li == NULL && sdp) { + sdp_media_t const *m; + + for (m = sdp->sdp_media; m; m = m->m_next) { + sdp_connection_t const *mc; + + if (m->m_rejected) + continue; + + for (mc = m->m_connections; mc; mc = mc->c_next) { + for (li = res; li; li = li->li_next) { + if (!su_casematch(li->li_canonname, sdp->sdp_connection->c_address)) + continue; +#if HAVE_SIN6 + if (li->li_family == AF_INET6) { + if (ip6 > ip4) + break; + else if (!li6) + li6 = li; /* Best IP6 address */ + } +#endif + else if (li->li_family == AF_INET) { + if (ip4 > ip6) + break; + else if (!li4) + li4 = li; /* Best IP4 address */ + } + } + } + + if (li) + break; + } + + if (li == NULL && ip4) + li = li4; + if (li == NULL && ip6) + li = li6; + if (li) + source = "an existing c= address from media descriptions"; + } + + /* Check if o= address is local */ + if (li == NULL && sdp && sdp->sdp_origin) { + char const *address = sdp->sdp_origin->o_address->c_address; + + for (li = res; li; li = li->li_next) { + if (!su_casematch(li->li_canonname, address)) + continue; +#if HAVE_SIN6 + if (li->li_family == AF_INET6) { + if (ip6 >= ip4) + break; + else if (!li6) + li6 = li; /* Best IP6 address */ + } +#endif + else if (li->li_family == AF_INET) { + if (ip4 >= ip6) + break; + else if (!li4) + li4 = li; /* Best IP4 address */ + } + } + + if (li == NULL && ip4) + li = li4; + if (li == NULL && ip6) + li = li6; + if (li) + source = "an existing address from o= line"; + } + + if (li == NULL) { + for (li = res; li; li = li->li_next) { +#if HAVE_SIN6 + if (li->li_family == AF_INET6) { + if (ip6 >= ip4) + break; + else if (!li6) + li6 = li; /* Best IP6 address */ + } +#endif + else if (li->li_family == AF_INET) { + if (ip4 >= ip6) + break; + else if (!li4) + li4 = li; /* Best IP4 address */ + } + } + + if (li == NULL && ip4) + li = li4; + if (li == NULL && ip6) + li = li6; + + if (li) + source = "a local address"; + } if (li) { + char const *typename; + + if (li->li_family == AF_INET) + c->c_nettype = sdp_net_in, c->c_addrtype = sdp_addr_ip4, typename = "IP4"; +#if HAVE_SIN6 + else if (li->li_family == AF_INET6) + c->c_nettype = sdp_net_in, c->c_addrtype = sdp_addr_ip6, typename = "IP6"; +#endif + else + typename = "???"; + assert(strlen(li->li_canonname) < 64); c->c_address = strcpy(buffer, li->li_canonname); + + SU_DEBUG_5(("%s: selected IN %s %s (%s)\n", __func__, + typename, li->li_canonname, source)); } su_freelocalinfo(res); @@ -2371,3 +2571,174 @@ soa_init_sdp_connection(soa_session_t *ss, else return 0; } + +/* Search for first entry from SOATAG_ADDRESS() list on localinfo list */ +static su_localinfo_t const * +best_listed_address_in_localinfo(su_localinfo_t const *res, + char const *address, + int ip4, + int ip6) +{ + su_localinfo_t const *li = NULL, *best = NULL; + size_t n; + + SU_DEBUG_3(("%s: searching for %s from list \"%s\"\n", + __func__, ip6 && !ip4 ? "IP6 " : !ip6 && ip4 ? "IP4 " : "", + address)); + + for (; address[0]; address += n + strspn(address, ", ")) { + n = strcspn(address, ", "); + if (n == 0) + continue; + + for (li = res; li; li = li->li_next) { + if (su_casenmatch(li->li_canonname, address, n) && + li->li_canonname[n] == '\0') + break; + } + + if (li == NULL) + continue; +#if HAVE_SIN6 + else if (li->li_family == AF_INET6) { + if (ip6 >= ip4) + return li; + else if (ip6 && !best) + best = li; /* Best IP6 address */ + } +#endif + else if (li->li_family == AF_INET) { + if (ip4 >= ip6) + return li; + else if (ip4 && !best) + best = li; /* Best IP4 address */ + } + } + + return best; +} + +/* Search for first entry from SOATAG_ADDRESS() list in session */ +static sdp_connection_t const * +best_listed_address_in_session(sdp_session_t const *sdp, + char const *address0, + int ip4, + int ip6) +{ + sdp_connection_t *c = NULL, *best = NULL; + sdp_media_t *m; + char const *address; + size_t n; + + for (address = address0; address[0]; address += n + strspn(address, ", ")) { + n = strcspn(address, ", "); + if (n == 0) + continue; + + c = sdp->sdp_connection; + + if (c && su_casenmatch(c->c_address, address, n) && c->c_address[n] == 0) + ; + else + for (m = sdp->sdp_media; m; m = m->m_next) { + if (m->m_connections && m->m_connections != sdp->sdp_connection) { + c = sdp->sdp_connection; + if (su_casenmatch(c->c_address, address, n) && c->c_address[n] == 0) + break; + c = NULL; + } + } + + if (c == NULL || c->c_nettype != sdp_net_in) + continue; +#if HAVE_SIN6 + else if (c->c_addrtype == sdp_addr_ip6) { + if (ip6 >= ip4) + return c; + else if (ip6 && !best) + best = c; /* Best IP6 address */ + } +#endif + else if (c->c_addrtype == sdp_addr_ip4) { + if (ip4 >= ip6) + return c; + else if (ip4 && !best) + best = c; /* Best IP4 address */ + } + } + + if (best || sdp->sdp_origin == NULL) + return best; + + /* Check if address on list is already been used on o= line */ + for (address = address0; address[0]; address += n + strspn(address, ", ")) { + n = strcspn(address, ", "); + if (n == 0) + continue; + c = sdp->sdp_origin->o_address; + + if (su_casenmatch(c->c_address, address, n) && c->c_address[n] != 0) + continue; +#if HAVE_SIN6 + else if (c->c_addrtype == sdp_addr_ip6) { + if (ip6 >= ip4) + return c; + else if (ip6 && !best) + best = c; /* Best IP6 address */ + } +#endif + else if (c->c_addrtype == sdp_addr_ip4) { + if (ip4 >= ip6) + return c; + else if (ip4 && !best) + best = c; /* Best IP4 address */ + } + } + + return best; +} + +static su_localinfo_t const * +best_listed_address(su_localinfo_t *li0, + char const *address, + int ip4, + int ip6) +{ + size_t n, best = 0; + char *buffer = (char *)li0->li_canonname; + + for (; address[0]; address += n + strspn(address + n, " ,")) { + if ((n = span_ip6_address(address))) { +#if SU_HAVE_IN6 + if (ip6 > ip4) { + li0->li_family = AF_INET6; + strncpy(buffer, address, n)[n] = '\0'; + return li0; + } + else if (ip6 && !best) { + li0->li_family = AF_INET6; + strncpy(buffer, address, best = n)[n] = '\0'; + } +#endif + } + else if ((n = span_ip4_address(address))) { + if (ip4 > ip6) { + li0->li_family = AF_INET; + strncpy(buffer, address, n)[n] = '\0'; + return li0; + } + else if (ip4 && !best) { + li0->li_family = AF_INET; + strncpy(buffer, address, best = n)[n] = '\0'; + } + } + else { + n = strcspn(address, " ,"); + } + } + + if (best) + return li0; + else + return NULL; +} diff --git a/libs/sofia-sip/libsofia-sip-ua/soa/soa_static.c b/libs/sofia-sip/libsofia-sip-ua/soa/soa_static.c index 2210d49760..aa3224325a 100644 --- a/libs/sofia-sip/libsofia-sip-ua/soa/soa_static.c +++ b/libs/sofia-sip/libsofia-sip-ua/soa/soa_static.c @@ -1092,7 +1092,6 @@ static int offer_answer_step(soa_session_t *ss, { soa_static_session_t *sss = (soa_static_session_t *)ss; - char c_address[64]; sdp_session_t *local = ss->ss_local->ssd_sdp; sdp_session_t local0[1]; @@ -1102,8 +1101,12 @@ static int offer_answer_step(soa_session_t *ss, sdp_session_t *remote = ss->ss_remote->ssd_sdp; unsigned remote_version = ss->ss_remote_version; + int fresh = 0; + sdp_origin_t o[1] = {{ sizeof(o) }}; sdp_connection_t *c, c0[1] = {{ sizeof(c0) }}; + char c0_buffer[64]; + sdp_time_t t[1] = {{ sizeof(t) }}; int *u2s = NULL, *s2u = NULL, *tbf; @@ -1150,22 +1153,16 @@ static int offer_answer_step(soa_session_t *ss, SU_DEBUG_7(("soa_static(%p, %s): %s\n", (void *)ss, by, "generating local description")); + fresh = 1; local = local0; *local = *user, local->sdp_media = NULL; - if (local->sdp_origin) { - o->o_username = local->sdp_origin->o_username; - /* o->o_address = local->sdp_origin->o_address; */ - } - if (!o->o_address) - o->o_address = c0; - local->sdp_origin = o; - - if (soa_init_sdp_origin(ss, o, c_address) < 0) { - phrase = "Cannot Get IP Address for Media"; - goto internal_error; - } + o->o_username = "-"; + o->o_address = c0; + c0->c_address = c0_buffer; + if (!local->sdp_origin) + local->sdp_origin = o; break; case process_answer: @@ -1283,28 +1280,46 @@ static int offer_answer_step(soa_session_t *ss, break; } - /* Step F: Update c= line */ + /* Step F0: Initialize o= line */ + if (fresh) { + if (user->sdp_origin) + o->o_username = user->sdp_origin->o_username; + + if (soa_init_sdp_origin_with_session(ss, o, c0_buffer, local) < 0) { + phrase = "Cannot Get IP Address for Session Description"; + goto internal_error; + } + + local->sdp_origin = o; + } + + /* Step F: Update c= line(s) */ switch (action) { + sdp_connection_t *user_c, *local_c; + case generate_offer: case generate_answer: - /* Upgrade local SDP based of user SDP */ - if (ss->ss_local_user_version == user_version && - local->sdp_connection) - break; + user_c = user->sdp_connection; + if (!soa_check_sdp_connection(user_c)) + user_c = NULL; - if (local->sdp_connection == NULL || - (user->sdp_connection != NULL && - sdp_connection_cmp(local->sdp_connection, user->sdp_connection))) { + local_c = local->sdp_connection; + if (!soa_check_sdp_connection(local_c)) + local_c = NULL; + + if (ss->ss_local_user_version != user_version || + local_c == NULL || + (user_c != NULL && sdp_connection_cmp(local_c, user_c))) { sdp_media_t *m; + if (user_c) + c = user_c; + else + c = local->sdp_origin->o_address; + /* Every m= line (even rejected one) must have a c= line * or there must be a c= line at session level */ - if (user->sdp_connection) - c = user->sdp_connection; - else - c = local->sdp_origin->o_address; - for (m = local->sdp_media; m; m = m->m_next) if (m->m_connections == NULL) break; diff --git a/libs/sofia-sip/libsofia-sip-ua/soa/sofia-sip/soa_session.h b/libs/sofia-sip/libsofia-sip-ua/soa/sofia-sip/soa_session.h index 0aa2830cb1..8691156dff 100644 --- a/libs/sofia-sip/libsofia-sip-ua/soa/sofia-sip/soa_session.h +++ b/libs/sofia-sip/libsofia-sip-ua/soa/sofia-sip/soa_session.h @@ -238,7 +238,11 @@ SOFIAPUBFUN int soa_has_received_sdp(soa_session_t const *ss); SOFIAPUBFUN int soa_set_status(soa_session_t *ss, int status, char const *phrase); -enum soa_activity { soa_activity_local, soa_activity_remote, soa_activity_session }; +enum soa_activity { + soa_activity_local, + soa_activity_remote, + soa_activity_session +}; SOFIAPUBFUN void soa_set_activity(soa_session_t *ss, sdp_media_t const *m, @@ -259,8 +263,18 @@ SOFIAPUBFUN int soa_description_dup(su_home_t *, SOFIAPUBFUN int soa_init_sdp_origin(soa_session_t *ss, sdp_origin_t *o, char buf[64]); +SOFIAPUBFUN int soa_init_sdp_origin_with_session(soa_session_t *ss, + sdp_origin_t *o, + char buffer[64], + sdp_session_t const *sdp); +SOFIAPUBFUN int soa_check_sdp_connection(sdp_connection_t const *c); SOFIAPUBFUN int soa_init_sdp_connection(soa_session_t *, sdp_connection_t *, char buf[64]); +SOFIAPUBFUN int soa_init_sdp_connection_with_session(soa_session_t *, + sdp_connection_t *, char buf[64], + sdp_session_t const *sdp); + +SOFIAPUBFUN sdp_connection_t *soa_find_local_sdp_connection(sdp_session_t const*); /* ====================================================================== */ /* Debug log settings */ diff --git a/libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c b/libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c index 20af22f78c..0305cf031f 100644 --- a/libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c +++ b/libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c @@ -55,6 +55,10 @@ struct context; #include #include +#include + +S2_LOCALINFO_STUBS(); + extern su_log_t soa_log[]; char const name[] = "test_soa"; @@ -72,6 +76,98 @@ int tstflags = 0; #define NONE ((void*)-1) +static char const *test_ifaces1[] = { + "eth0\0" "11.12.13.14\0" "2001:1508:1003::21a:a0ff:fe71:813\0" "fe80::21a:a0ff:fe71:813\0", + "eth1\0" "12.13.14.15\0" "2001:1508:1004::21a:a0ff:fe71:814\0" "fe80::21a:a0ff:fe71:814\0", + "eth2\0" "192.168.2.15\0" "fec0::21a:a0ff:fe71:815\0" "fe80::21a:a0ff:fe71:815\0", + "lo0\0" "127.0.0.1\0" "::1\0", + NULL +}; + +int test_localinfo_replacement(void) +{ + BEGIN(); + su_localinfo_t *res, *li, hints[1]; + int error, n; + struct results { + struct afresult { unsigned global, site, link, host; } ip6[1], ip4[1]; + } results[1]; + + s2_localinfo_ifaces(test_ifaces1); + + error = su_getlocalinfo(NULL, &res); + TEST(error, ELI_NOERROR); + TEST_1(res != NULL); + memset(results, 0, sizeof results); + for (li = res, n = 0; li; li = li->li_next) { + struct afresult *afr; + TEST_1(li->li_family == AF_INET || li->li_family == AF_INET6); + if (li->li_family == AF_INET) + afr = results->ip4; + else + afr = results->ip6; + + if (li->li_scope == LI_SCOPE_GLOBAL) + afr->global++; + else if (li->li_scope == LI_SCOPE_SITE) + afr->site++; + else if (li->li_scope == LI_SCOPE_LINK) + afr->link++; + else if (li->li_scope == LI_SCOPE_HOST) + afr->host++; + n++; + } + TEST(n, 11); + TEST(results->ip4->global, 2); + TEST(results->ip4->site, 1); + TEST(results->ip4->link, 0); + TEST(results->ip4->host, 1); +#if SU_HAVE_IN6 + TEST(results->ip6->global, 2); + TEST(results->ip6->site, 1); + TEST(results->ip6->link, 3); + TEST(results->ip6->host, 1); +#endif + su_freelocalinfo(res); + + error = su_getlocalinfo(memset(hints, 0, sizeof hints), &res); + TEST(error, ELI_NOERROR); + TEST_1(res != NULL); + for (li = res, n = 0; li; li = li->li_next) + n++; + TEST(n, 11); + su_freelocalinfo(res); + + hints->li_flags = LI_CANONNAME; + + error = su_getlocalinfo(hints, &res); + TEST(error, ELI_NOERROR); + TEST_1(res != NULL); + for (li = res, n = 0; li; li = li->li_next) { + TEST_1(li->li_canonname != NULL); + n++; + } + TEST(n, 11); + su_freelocalinfo(res); + + hints->li_flags = LI_IFNAME | LI_CANONNAME; + hints->li_ifname = "eth1"; + + error = su_getlocalinfo(hints, &res); + TEST(error, ELI_NOERROR); + TEST_1(res != NULL); + for (li = res, n = 0; li; li = li->li_next) { + TEST_1(li->li_canonname != NULL); + TEST_S(li->li_ifname, "eth1"); + n++; + } + TEST(n, 3); + su_freelocalinfo(res); + + END(); +} +/* ========================================================================= */ + struct context { su_home_t home[1]; @@ -1871,6 +1967,463 @@ int test_asynch_offer_answer(struct context *ctx) END(); } +#define TEST_OC_ADDRESS(s, address, ip) \ + TEST(test_address_in_offer(s, address, sdp_addr_ ## ip, address, sdp_addr_ ## ip), 0); + +static int +test_address_in_offer(soa_session_t *ss, + char const *o_address, + int o_addrtype, + char const *c_address, + int c_addrtype) +{ + sdp_session_t const *sdp = NULL; + sdp_connection_t const *c; + + TEST(soa_get_local_sdp(ss, &sdp, NULL, NULL), 1); + TEST_1(sdp != NULL); + TEST_1(c = sdp->sdp_connection); + TEST(c->c_nettype, sdp_net_in); + if (c_addrtype) TEST(c->c_addrtype, c_addrtype); + if (c_address) TEST_S(c->c_address, c_address); + + TEST_1(c = sdp->sdp_origin->o_address); + TEST(c->c_nettype, sdp_net_in); + if (o_addrtype) TEST(c->c_addrtype, o_addrtype); + if (o_address) TEST_S(c->c_address, o_address); + + return 0; +} + +/** This tests the IP address selection logic. + * + * The IP address is selected based on the SOATAG_AF() preference, + * SOATAG_ADDRESS(), and locally obtained address list. + */ +int test_address_selection(struct context *ctx) +{ + BEGIN(); + int n; + + static char const *ifaces1[] = { + "eth2\0" "192.168.2.15\0" "fec0::21a:a0ff:fe71:815\0" "fe80::21a:a0ff:fe71:815\0", + "eth0\0" "11.12.13.14\0" "2001:1508:1003::21a:a0ff:fe71:813\0" "fe80::21a:a0ff:fe71:813\0", + "eth1\0" "12.13.14.15\0" "2001:1508:1004::21a:a0ff:fe71:814\0" "fe80::21a:a0ff:fe71:814\0", + "lo0\0" "127.0.0.1\0" "::1\0", + NULL + }; + + static char const *ifaces_ip6only[] = { + "eth2\0" "fec0::21a:a0ff:fe71:815\0" "fe80::21a:a0ff:fe71:815\0", + "eth0\0" "2001:1508:1003::21a:a0ff:fe71:813\0" "fe80::21a:a0ff:fe71:813\0", + "eth1\0" "2001:1508:1004::21a:a0ff:fe71:814\0" "fe80::21a:a0ff:fe71:814\0", + "lo0\0" "127.0.0.1\0" "::1\0", + NULL + }; + + static char const *ifaces_ip4only[] = { + "eth2\0" "192.168.2.15\0" "fe80::21a:a0ff:fe71:815\0", + "eth0\0" "11.12.13.14\0" "fe80::21a:a0ff:fe71:813\0", + "eth1\0" "12.13.14.15\0" "fe80::21a:a0ff:fe71:814\0", + "lo0\0" "127.0.0.1\0" "::1\0", + NULL + }; + + soa_session_t *a, *b; + sdp_origin_t *o; + + su_home_t home[1] = { SU_HOME_INIT(home) }; + + s2_localinfo_ifaces(ifaces1); + + TEST_1(a = soa_clone(ctx->a, ctx->root, ctx)); + + /* SOATAG_AF(SOA_AF_IP4_ONLY) => select IP4 address */ + n = soa_set_params(a, SOATAG_AF(SOA_AF_IP4_ONLY), TAG_END()); + n = soa_set_user_sdp(a, 0, "m=audio 5008 RTP/AVP 0 8", -1); TEST(n, 1); + n = soa_generate_offer(a, 1, test_completed); TEST(n, 0); + TEST_OC_ADDRESS(a, "11.12.13.14", ip4); + /* Should flush the session */ + TEST_VOID(soa_process_reject(a, NULL)); + + /* SOATAG_AF(SOA_AF_IP6_ONLY) => select IP6 address */ + n = soa_set_params(a, SOATAG_AF(SOA_AF_IP6_ONLY), TAG_END()); + n = soa_generate_offer(a, 1, test_completed); TEST(n, 0); + TEST_OC_ADDRESS(a, "2001:1508:1003::21a:a0ff:fe71:813", ip6); + TEST_VOID(soa_terminate(a, NULL)); + + /* SOATAG_AF(SOA_AF_IP4_IP6) => select IP4 address */ + n = soa_set_params(a, SOATAG_AF(SOA_AF_IP4_IP6), TAG_END()); + n = soa_generate_offer(a, 1, test_completed); TEST(n, 0); + TEST_OC_ADDRESS(a, "11.12.13.14", ip4); + TEST_VOID(soa_process_reject(a, NULL)); + + /* SOATAG_AF(SOA_AF_IP6_IP4) => select IP6 address */ + n = soa_set_params(a, SOATAG_AF(SOA_AF_IP6_IP4), TAG_END()); + n = soa_generate_offer(a, 1, test_completed); TEST(n, 0); + TEST_OC_ADDRESS(a, "2001:1508:1003::21a:a0ff:fe71:813", ip6); + TEST_VOID(soa_terminate(a, NULL)); + + /* SOATAG_AF(SOA_AF_IP4_IP6) but session mentions IP6 => select IP6 */ + n = soa_set_params(a, SOATAG_AF(SOA_AF_IP4_IP6), TAG_END()); + n = soa_set_user_sdp(a, 0, "c=IN IP6 ::\r\nm=audio 5008 RTP/AVP 0 8", -1); TEST(n, 1); + n = soa_generate_offer(a, 1, test_completed); TEST(n, 0); + TEST_OC_ADDRESS(a, "2001:1508:1003::21a:a0ff:fe71:813", ip6); + TEST_VOID(soa_terminate(a, NULL)); + + /* SOATAG_AF(SOA_AF_IP4_IP6), o= mentions IP6 => select IP4 */ + n = soa_set_user_sdp(a, 0, "o=- 1 1 IN IP6 ::\r\n" + "m=audio 5008 RTP/AVP 0 8", -1); TEST(n, 1); + n = soa_generate_offer(a, 1, test_completed); TEST(n, 0); + TEST_OC_ADDRESS(a, "11.12.13.14", ip4); + TEST_VOID(soa_process_reject(a, NULL)); + + /* SOATAG_AF(SOA_AF_IP4_IP6), c= uses non-local IP6 + => select local IP6 on o= */ + n = soa_set_user_sdp(a, 0, + "c=IN IP6 2001:1508:1004::21a:a0ff:fe71:819\r\n" + "m=audio 5008 RTP/AVP 0 8", -1); + TEST(n, 1); + n = soa_generate_offer(a, 1, test_completed); TEST(n, 0); + TEST(test_address_in_offer(a, + /* o= has local address */ + "2001:1508:1003::21a:a0ff:fe71:813", sdp_addr_ip6, + /* c= has sdp-provided address */ + "2001:1508:1004::21a:a0ff:fe71:819", sdp_addr_ip6), 0); + TEST_VOID(soa_terminate(a, NULL)); + + /* SOATAG_AF(SOA_AF_IP4_ONLY), no IP4 addresses */ + s2_localinfo_ifaces(ifaces_ip6only); + n = soa_set_params(a, SOATAG_AF(SOA_AF_IP4_ONLY), TAG_END()); + n = soa_set_user_sdp(a, 0, "m=audio 5008 RTP/AVP 0 8", -1); + TEST(soa_generate_offer(a, 1, test_completed), -1); + + /* Retry with IP6 enabled */ + n = soa_set_params(a, SOATAG_AF(SOA_AF_IP4_IP6), TAG_END()); + TEST(soa_generate_offer(a, 1, test_completed), 0); + TEST_OC_ADDRESS(a, "2001:1508:1003::21a:a0ff:fe71:813", ip6); + TEST_VOID(soa_terminate(a, NULL)); + + /* SOATAG_AF(SOA_AF_IP6_ONLY), no IP6 addresses */ + s2_localinfo_ifaces(ifaces_ip4only); + n = soa_set_params(a, SOATAG_AF(SOA_AF_IP6_ONLY), TAG_END()); + TEST(soa_generate_offer(a, 1, test_completed), -1); /* should fail */ + TEST_VOID(soa_terminate(a, NULL)); + + /* SOATAG_AF(SOA_AF_IP4_ONLY), no IP4 addresses */ + s2_localinfo_ifaces(ifaces_ip6only); + n = soa_set_params(a, SOATAG_AF(SOA_AF_IP4_ONLY), TAG_END()); + TEST(soa_generate_offer(a, 1, test_completed), -1); /* should fail */ + TEST_VOID(soa_terminate(a, NULL)); + + /* Select locally available address from the SOATAG_ADDRESS() list */ + s2_localinfo_ifaces(ifaces1); + n = soa_set_params(a, SOATAG_AF(SOA_AF_IP4_IP6), + SOATAG_ADDRESS("test.com 17.18.19.20 12.13.14.15"), + TAG_END()); + n = soa_generate_offer(a, 1, test_completed); TEST(n, 0); + TEST_OC_ADDRESS(a, "12.13.14.15", ip4); + TEST_VOID(soa_process_reject(a, NULL)); + + /* Select locally available IP6 address from the SOATAG_ADDRESS() list */ + n = soa_set_params(a, SOATAG_AF(SOA_AF_IP6_IP4), + SOATAG_ADDRESS("test.com 12.13.14.15 fec0::21a:a0ff:fe71:815"), + TAG_END()); + n = soa_generate_offer(a, 1, test_completed); TEST(n, 0); + TEST_OC_ADDRESS(a, "fec0::21a:a0ff:fe71:815", ip6); + TEST_VOID(soa_process_reject(a, NULL)); + + /* Select first available address from the SOATAG_ADDRESS() list */ + n = soa_set_params(a, SOATAG_AF(SOA_AF_ANY), + SOATAG_ADDRESS("test.com 12.13.14.15 fec0::21a:a0ff:fe71:815"), + TAG_END()); + n = soa_generate_offer(a, 1, test_completed); TEST(n, 0); + TEST_OC_ADDRESS(a, "12.13.14.15", ip4); + TEST_VOID(soa_process_reject(a, NULL)); + + /* Select preferred address from the SOATAG_ADDRESS() list */ + s2_localinfo_ifaces(ifaces1); + n = soa_set_params(a, SOATAG_AF(SOA_AF_IP4_IP6), + SOATAG_ADDRESS("test.com fec0::22a:a0ff:fe71:815 19.18.19.20"), + TAG_END()); + n = soa_generate_offer(a, 1, test_completed); TEST(n, 0); + TEST_OC_ADDRESS(a, "19.18.19.20", ip4); + TEST_VOID(soa_process_reject(a, NULL)); + + /* Select preferred address from the SOATAG_ADDRESS() list */ + s2_localinfo_ifaces(ifaces1); + n = soa_set_params(a, SOATAG_AF(SOA_AF_IP6_IP4), + SOATAG_ADDRESS("test.com 19.18.19.20 fec0::22a:a0ff:fe71:815 fec0::22a:a0ff:fe71:819"), + TAG_END()); + n = soa_generate_offer(a, 1, test_completed); TEST(n, 0); + TEST_OC_ADDRESS(a, "fec0::22a:a0ff:fe71:815", ip6); + TEST_VOID(soa_process_reject(a, NULL)); + + TEST_VOID(soa_destroy(a)); + + (void)b; (void)o; +#if 0 + TEST_1(b = soa_clone(ctx->b, ctx->root, ctx)); + + n = soa_set_remote_sdp(b, 0, offer, offerlen); TEST(n, 1); + + n = soa_get_local_sdp(b, NULL, &answer, &answerlen); TEST(n, 0); + + n = soa_set_params(b, + SOATAG_LOCAL_SDP_STR("m=audio 5004 RTP/AVP 8"), + SOATAG_AF(SOA_AF_IP4_ONLY), + SOATAG_ADDRESS("1.2.3.4"), + TAG_END()); + + n = soa_generate_answer(b, test_completed); TEST(n, 0); + + TEST_1(soa_is_complete(b)); + TEST(soa_activate(b, NULL), 0); + + n = soa_get_local_sdp(b, NULL, &answer, &answerlen); TEST(n, 1); + TEST_1(answer != NULL && answer != NONE); + TEST_1(strstr(answer, "c=IN IP4 1.2.3.4")); + + n = soa_set_remote_sdp(a, 0, answer, -1); TEST(n, 1); + + n = soa_process_answer(a, test_completed); TEST(n, 0); + + TEST_1(soa_is_complete(a)); + TEST(soa_activate(a, NULL), 0); + + TEST(soa_is_audio_active(a), SOA_ACTIVE_SENDRECV); + TEST(soa_is_video_active(a), SOA_ACTIVE_DISABLED); + TEST(soa_is_image_active(a), SOA_ACTIVE_DISABLED); + TEST(soa_is_chat_active(a), SOA_ACTIVE_DISABLED); + + TEST(soa_is_remote_audio_active(a), SOA_ACTIVE_SENDRECV); + TEST(soa_is_remote_video_active(a), SOA_ACTIVE_DISABLED); + TEST(soa_is_remote_image_active(a), SOA_ACTIVE_DISABLED); + TEST(soa_is_remote_chat_active(a), SOA_ACTIVE_DISABLED); + + /* 'A' will put call on hold */ + offer = NONE; + TEST(soa_set_params(a, SOATAG_HOLD("*"), TAG_END()), 1); + + TEST(soa_generate_offer(a, 1, test_completed), 0); + TEST(soa_get_local_sdp(a, NULL, &offer, &offerlen), 1); + TEST_1(offer != NULL && offer != NONE); + TEST_1(strstr(offer, "a=sendonly")); + TEST(soa_set_remote_sdp(b, 0, offer, offerlen), 1); + TEST(soa_generate_answer(b, test_completed), 0); + TEST_1(soa_is_complete(b)); + TEST(soa_activate(b, NULL), 0); + TEST(soa_get_local_sdp(b, NULL, &answer, &answerlen), 1); + TEST_1(answer != NULL && answer != NONE); + TEST_1(strstr(answer, "a=recvonly")); + TEST(soa_set_remote_sdp(a, 0, answer, -1), 1); + TEST(soa_process_answer(a, test_completed), 0); + TEST(soa_activate(a, NULL), 0); + + TEST(soa_is_audio_active(a), SOA_ACTIVE_SENDONLY); + TEST(soa_is_remote_audio_active(a), SOA_ACTIVE_SENDONLY); + + /* 'A' will put call inactive */ + offer = NONE; + TEST(soa_set_params(a, SOATAG_HOLD("#"), TAG_END()), 1); + + TEST(soa_generate_offer(a, 1, test_completed), 0); + TEST(soa_get_local_sdp(a, NULL, &offer, &offerlen), 1); + TEST_1(offer != NULL && offer != NONE); + TEST_1(strstr(offer, "a=inactive")); + TEST(soa_set_remote_sdp(b, 0, offer, offerlen), 1); + TEST(soa_generate_answer(b, test_completed), 0); + TEST_1(soa_is_complete(b)); + TEST(soa_activate(b, NULL), 0); + TEST(soa_get_local_sdp(b, NULL, &answer, &answerlen), 1); + TEST_1(answer != NULL && answer != NONE); + TEST_1(strstr(answer, "a=inactive")); + TEST(soa_set_remote_sdp(a, 0, answer, -1), 1); + TEST(soa_process_answer(a, test_completed), 0); + TEST(soa_activate(a, NULL), 0); + + TEST(soa_is_audio_active(a), SOA_ACTIVE_INACTIVE); + TEST(soa_is_remote_audio_active(a), SOA_ACTIVE_INACTIVE); + + /* B will send an offer to A, but there is no change in O/A status */ + TEST(soa_generate_offer(b, 1, test_completed), 0); + TEST(soa_get_local_sdp(b, NULL, &offer, &offerlen), 1); + TEST_1(offer != NULL && offer != NONE); + TEST_1(!strstr(offer, "a=inactive")); + /* printf("offer:\n%s", offer); */ + TEST(soa_set_remote_sdp(a, 0, offer, offerlen), 1); + TEST(soa_is_remote_audio_active(a), SOA_ACTIVE_SENDRECV); + TEST(soa_generate_answer(a, test_completed), 0); + TEST(soa_is_audio_active(a), SOA_ACTIVE_INACTIVE); + TEST(soa_is_remote_audio_active(a), SOA_ACTIVE_INACTIVE); + TEST_1(soa_is_complete(a)); + TEST(soa_activate(a, NULL), 0); + TEST(soa_get_local_sdp(a, NULL, &answer, &answerlen), 1); + TEST_1(answer != NULL && answer != NONE); + TEST_1(strstr(answer, "a=inactive")); + /* printf("answer:\n%s", answer); */ + TEST(soa_set_remote_sdp(b, 0, answer, -1), 1); + TEST(soa_process_answer(b, test_completed), 0); + TEST(soa_activate(b, NULL), 0); + + + TEST(soa_is_audio_active(b), SOA_ACTIVE_INACTIVE); + TEST(soa_is_remote_audio_active(b), SOA_ACTIVE_INACTIVE); + + /* 'A' will release hold. */ + TEST(soa_set_params(a, SOATAG_HOLD(NULL), TAG_END()), 1); + + TEST(soa_generate_offer(a, 1, test_completed), 0); + TEST(soa_get_local_sdp(a, NULL, &offer, &offerlen), 1); + TEST_1(offer != NULL && offer != NONE); + TEST_1(!strstr(offer, "a=sendonly") && !strstr(offer, "a=inactive")); + TEST(soa_set_remote_sdp(b, 0, offer, offerlen), 1); + TEST(soa_generate_answer(b, test_completed), 0); + TEST_1(soa_is_complete(b)); + TEST(soa_activate(b, NULL), 0); + TEST(soa_get_local_sdp(b, NULL, &answer, &answerlen), 1); + TEST_1(answer != NULL && answer != NONE); + TEST_1(!strstr(answer, "a=recvonly") && !strstr(answer, "a=inactive")); + TEST(soa_set_remote_sdp(a, 0, answer, -1), 1); + TEST(soa_process_answer(a, test_completed), 0); + TEST(soa_activate(a, NULL), 0); + + TEST(soa_is_audio_active(a), SOA_ACTIVE_SENDRECV); + TEST(soa_is_remote_audio_active(a), SOA_ACTIVE_SENDRECV); + + /* 'A' will put B on hold but this time with c=IN IP4 0.0.0.0 */ + TEST(soa_set_params(a, SOATAG_HOLD("*"), TAG_END()), 1); + TEST(soa_generate_offer(a, 1, test_completed), 0); + + { + sdp_session_t const *o_sdp; + sdp_session_t *sdp; + sdp_printer_t *p; + sdp_connection_t *c; + + TEST(soa_get_local_sdp(a, &o_sdp, NULL, NULL), 1); + TEST_1(o_sdp != NULL && o_sdp != NONE); + TEST_1(sdp = sdp_session_dup(home, o_sdp)); + + /* Remove mode, change c=, encode offer */ + if (sdp->sdp_media->m_connections) + c = sdp->sdp_media->m_connections; + else + c = sdp->sdp_connection; + TEST_1(c); + c->c_address = "0.0.0.0"; + + TEST_1(p = sdp_print(home, sdp, NULL, 0, sdp_f_realloc)); + TEST_1(sdp_message(p)); + offer = sdp_message(p); offerlen = strlen(offer); + } + + TEST(soa_set_remote_sdp(b, 0, offer, -1), 1); + TEST(soa_generate_answer(b, test_completed), 0); + TEST_1(soa_is_complete(b)); + TEST(soa_activate(b, NULL), 0); + TEST(soa_get_local_sdp(b, NULL, &answer, &answerlen), 1); + TEST_1(answer != NULL && answer != NONE); + TEST_1(strstr(answer, "a=recvonly")); + TEST(soa_set_remote_sdp(a, 0, answer, -1), 1); + TEST(soa_process_answer(a, test_completed), 0); + TEST(soa_activate(a, NULL), 0); + + TEST(soa_is_audio_active(a), SOA_ACTIVE_SENDONLY); + TEST(soa_is_remote_audio_active(a), SOA_ACTIVE_SENDONLY); + TEST(soa_is_audio_active(b), SOA_ACTIVE_RECVONLY); + TEST(soa_is_remote_audio_active(b), SOA_ACTIVE_RECVONLY); + + /* 'A' will propose adding video. */ + /* 'B' will reject. */ + TEST(soa_set_params(a, + SOATAG_HOLD(NULL), /* 'A' will release hold. */ + SOATAG_USER_SDP_STR("m=audio 5008 RTP/AVP 0 8\r\ni=x\r\n" + "m=video 5006 RTP/AVP 34\r\n"), + TAG_END()), 2); + + TEST(soa_generate_offer(a, 1, test_completed), 0); + TEST(soa_get_local_sdp(a, NULL, &offer, &offerlen), 1); + TEST_1(offer != NULL && offer != NONE); + TEST_1(!strstr(offer, "a=sendonly")); + TEST_1(strstr(offer, "m=video")); + TEST(soa_set_remote_sdp(b, 0, offer, offerlen), 1); + TEST(soa_generate_answer(b, test_completed), 0); + TEST_1(soa_is_complete(b)); + TEST(soa_activate(b, NULL), 0); + TEST(soa_get_local_sdp(b, NULL, &answer, &answerlen), 1); + TEST_1(answer != NULL && answer != NONE); + TEST_1(!strstr(answer, "a=recvonly")); + TEST_1(strstr(answer, "m=video")); + TEST(soa_set_remote_sdp(a, 0, answer, -1), 1); + TEST(soa_process_answer(a, test_completed), 0); + TEST(soa_activate(a, NULL), 0); + + TEST(soa_is_audio_active(a), SOA_ACTIVE_SENDRECV); + TEST(soa_is_remote_audio_active(a), SOA_ACTIVE_SENDRECV); + TEST(soa_is_video_active(a), SOA_ACTIVE_REJECTED); + + { + /* Test tags */ + sdp_session_t const *l = NULL, *u = NULL, *r = NULL; + sdp_media_t const *m; + + TEST(soa_get_params(b, + SOATAG_LOCAL_SDP_REF(l), + SOATAG_USER_SDP_REF(u), + SOATAG_REMOTE_SDP_REF(r), + TAG_END()), 3); + + TEST_1(l); TEST_1(u); TEST_1(r); + TEST_1(m = l->sdp_media); TEST(m->m_type, sdp_media_audio); + TEST_1(!m->m_rejected); + TEST_1(m = m->m_next); TEST(m->m_type, sdp_media_video); + TEST_1(m->m_rejected); + } + + /* 'B' will now propose adding video. */ + /* 'A' will accept. */ + TEST(soa_set_params(b, + SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8\r\n" + "m=video 5006 RTP/AVP 34\r\n"), + TAG_END()), 1); + + TEST(soa_generate_offer(b, 1, test_completed), 0); + TEST(soa_get_local_sdp(b, NULL, &offer, &offerlen), 1); + TEST_1(offer != NULL && offer != NONE); + TEST_1(!strstr(offer, "b=sendonly")); + TEST_1(strstr(offer, "m=video")); + TEST(soa_set_remote_sdp(a, 0, offer, offerlen), 1); + TEST(soa_generate_answer(a, test_completed), 0); + TEST_1(soa_is_complete(a)); + TEST(soa_activate(a, NULL), 0); + TEST(soa_get_local_sdp(a, NULL, &answer, &answerlen), 1); + TEST_1(answer != NULL && answer != NONE); + TEST_1(!strstr(answer, "b=recvonly")); + TEST_1(strstr(answer, "m=video")); + TEST(soa_set_remote_sdp(b, 0, answer, -1), 1); + TEST(soa_process_answer(b, test_completed), 0); + TEST(soa_activate(b, NULL), 0); + + TEST(soa_is_audio_active(a), SOA_ACTIVE_SENDRECV); + TEST(soa_is_remote_audio_active(a), SOA_ACTIVE_SENDRECV); + TEST(soa_is_video_active(a), SOA_ACTIVE_SENDRECV); + + TEST_VOID(soa_terminate(a, NULL)); + + TEST(soa_is_audio_active(a), SOA_ACTIVE_DISABLED); + TEST(soa_is_remote_audio_active(a), SOA_ACTIVE_DISABLED); + + TEST_VOID(soa_terminate(b, NULL)); + + TEST_VOID(soa_destroy(a)); + TEST_VOID(soa_destroy(b)); +#endif + su_home_deinit(home); + + END(); +} + int test_deinit(struct context *ctx) { BEGIN(); @@ -1974,10 +2527,14 @@ int main(int argc, char *argv[]) if (retval && quit_on_single_failure) { su_deinit(); return retval; } \ } while(0) + retval |= test_localinfo_replacement(); SINGLE_FAILURE_CHECK(); + retval |= test_api_errors(ctx); SINGLE_FAILURE_CHECK(); retval |= test_soa_tags(ctx); SINGLE_FAILURE_CHECK(); retval |= test_init(ctx, argv + i); SINGLE_FAILURE_CHECK(); + if (retval == 0) { + retval |= test_address_selection(ctx); SINGLE_FAILURE_CHECK(); retval |= test_params(ctx); SINGLE_FAILURE_CHECK(); retval |= test_static_offer_answer(ctx); SINGLE_FAILURE_CHECK();