diff options
43 files changed, 4219 insertions, 839 deletions
diff --git a/UPGRADE-15.txt b/UPGRADE-15.txt new file mode 100644 index 000000000..4ebe400a6 --- /dev/null +++ b/UPGRADE-15.txt @@ -0,0 +1,85 @@ +=========================================================== +=== +=== Information for upgrading between Asterisk versions +=== +=== These files document all the changes that MUST be taken +=== into account when upgrading between the Asterisk +=== versions listed below. These changes may require that +=== you modify your configuration files, dialplan or (in +=== some cases) source code if you have your own Asterisk +=== modules or patches. These files also include advance +=== notice of any functionality that has been marked as +=== 'deprecated' and may be removed in a future release, +=== along with the suggested replacement functionality. +=== +=== UPGRADE-1.2.txt -- Upgrade info for 1.0 to 1.2 +=== UPGRADE-1.4.txt -- Upgrade info for 1.2 to 1.4 +=== UPGRADE-1.6.txt -- Upgrade info for 1.4 to 1.6 +=== UPGRADE-1.8.txt -- Upgrade info for 1.6 to 1.8 +=== UPGRADE-10.txt -- Upgrade info for 1.8 to 10 +=== UPGRADE-11.txt -- Upgrade info for 10 to 11 +=== UPGRADE-12.txt -- Upgrade info for 11 to 12 +=== UPGRADE-13.txt -- Upgrade info for 12 to 13 +=== UPGRADE-14.txt -- Upgrade info for 13 to 14 +=== UPGRADE-15.txt -- Upgrade info for 14 to 15 +=========================================================== + +New in 15.0.0: + +Core: + - The 'Data Retrieval API' has been removed. This API was not actively + maintained, was not added to new modules (such as res_pjsip), and there + exist better alternatives to acquire the same information, such as the + ARI. As a result, the 'DataGet' AMI action as well as the 'data get' + CLI command have been removed. + +From 14.6.0 to 14.7.0: + +Core: + - ast_app_parse_timelen now returns an error if it encounters extra characters + at the end of the string to be parsed. + +From 14.4.0 to 14.5.0: + +Core: + - Support for embedded modules has been removed. This has not worked in + many years. LOADABLE_MODULES menuselect option is also removed as + loadable module support is now always enabled. + +From 14.3.0 to 14.4.0: + +res_rtp_asterisk: + - The RTP layer of Asterisk now has support for RFC 5761: "Multiplexing RTP + Data and Control Packets on a Single Port." For the PJSIP channel driver, + chan_pjsip, you can set "rtcp_mux = yes" on a PJSIP endpoint in pjsip.conf + to enable the feature. For chan_sip you can set "rtcp_mux = yes" either + globally or on a per-peer basis in sip.conf. + +New in 14.0.0 + +ARI: + - The policy for when to send "Dial" events has changed. Previously, "Dial" + events were sent on the calling channel's topic. However, starting in Asterisk + 14, if there is no calling channel on which to send the event, the event is + instead sent on the called channel's topic. Note that for the ARI channels + resource's dial operation, this means that the "Dial" events will always be + sent on the called channel's topic. + +Queue: + - When reloading the members of a queue, the members added dynamically (i.e. + added via the CLI command "queue add" or the AMI action "QueueAdd") now have + their ringinuse value updated to the value of the queue. Previously, the + ringinuse value for dynamic members was not updated on reload. + +Queue log: + - New RINGCANCELED event is logged when the caller hangs up while ringing. + The data1 field contains number of miliseconds since start of ringing. + +Channel Drivers: + +chan_dahdi: + - Support for specifying a DAHDI channel using a path under /dev/dahdi + ("by name") has been removed. It was never used. Instead you should + use kernel-level channel number allocation using span assignments. + See the documentation of dahdi-linux and dahdi-tools. + diff --git a/UPGRADE.txt b/UPGRADE.txt index eb05b035e..87eabde2d 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -21,58 +21,5 @@ === UPGRADE-12.txt -- Upgrade info for 11 to 12 === UPGRADE-13.txt -- Upgrade info for 12 to 13 === UPGRADE-14.txt -- Upgrade info for 13 to 14 +=== UPGRADE-15.txt -- Upgrade info for 14 to 15 =========================================================== - -New in 15.0.0: - -Core: - - The 'Data Retrieval API' has been removed. This API was not actively - maintained, was not added to new modules (such as res_pjsip), and there - exist better alternatives to acquire the same information, such as the - ARI. As a result, the 'DataGet' AMI action as well as the 'data get' - CLI command have been removed. - -From 14.4.0 to 14.5.0: - -Core: - - Support for embedded modules has been removed. This has not worked in - many years. LOADABLE_MODULES menuselect option is also removed as - loadable module support is now always enabled. - -From 14.3.0 to 14.4.0: - -res_rtp_asterisk: - - The RTP layer of Asterisk now has support for RFC 5761: "Multiplexing RTP - Data and Control Packets on a Single Port." For the PJSIP channel driver, - chan_pjsip, you can set "rtcp_mux = yes" on a PJSIP endpoint in pjsip.conf - to enable the feature. For chan_sip you can set "rtcp_mux = yes" either - globally or on a per-peer basis in sip.conf. - -New in 14.0.0 - -ARI: - - The policy for when to send "Dial" events has changed. Previously, "Dial" - events were sent on the calling channel's topic. However, starting in Asterisk - 14, if there is no calling channel on which to send the event, the event is - instead sent on the called channel's topic. Note that for the ARI channels - resource's dial operation, this means that the "Dial" events will always be - sent on the called channel's topic. - -Queue: - - When reloading the members of a queue, the members added dynamically (i.e. - added via the CLI command "queue add" or the AMI action "QueueAdd") now have - their ringinuse value updated to the value of the queue. Previously, the - ringinuse value for dynamic members was not updated on reload. - -Queue log: - - New RINGCANCELED event is logged when the caller hangs up while ringing. - The data1 field contains number of miliseconds since start of ringing. - -Channel Drivers: - -chan_dahdi: - - Support for specifying a DAHDI channel using a path under /dev/dahdi - ("by name") has been removed. It was never used. Instead you should - use kernel-level channel number allocation using span assignments. - See the documentation of dahdi-linux and dahdi-tools. - diff --git a/apps/app_playback.c b/apps/app_playback.c index 35900e8f7..7c895e362 100644 --- a/apps/app_playback.c +++ b/apps/app_playback.c @@ -322,7 +322,7 @@ static int say_date_generic(struct ast_channel *chan, time_t t, if (format == NULL) format = ""; - ast_localtime(&when, &tm, NULL); + ast_localtime(&when, &tm, timezonename); snprintf(buf, sizeof(buf), "%s:%s:%04d%02d%02d%02d%02d.%02d-%d-%3d", prefix, format, diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c index 956b951fd..0a07cc103 100644 --- a/apps/app_voicemail.c +++ b/apps/app_voicemail.c @@ -11064,7 +11064,7 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_ int skipuser, int max_logins, int silent) { int useadsi = 0, valid = 0, logretries = 0; - char password[AST_MAX_EXTENSION]="", *passptr; + char password[AST_MAX_EXTENSION], *passptr; struct ast_vm_user vmus, *vmu = NULL; /* If ADSI is supported, setup login screen */ @@ -11106,7 +11106,8 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_ adsi_password(chan); if (!ast_strlen_zero(prefix)) { - char fullusername[80] = ""; + char fullusername[80]; + ast_copy_string(fullusername, prefix, sizeof(fullusername)); strncat(fullusername, mailbox, sizeof(fullusername) - 1 - strlen(fullusername)); ast_copy_string(mailbox, fullusername, mailbox_size); @@ -11164,6 +11165,10 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_ free_user(vmu); return -1; } + if (ast_waitstream(chan, "")) { /* Channel is hung up */ + free_user(vmu); + return -1; + } } else { if (useadsi) adsi_login(chan); @@ -11173,10 +11178,6 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_ return -1; } } - if (ast_waitstream(chan, "")) { /* Channel is hung up */ - free_user(vmu); - return -1; - } } } if (!valid && (logretries >= max_logins)) { diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c index 132ff0822..c5428a854 100644 --- a/bridges/bridge_softmix.c +++ b/bridges/bridge_softmix.c @@ -524,15 +524,32 @@ static int append_all_streams(struct ast_stream_topology *dest, const struct ast_stream_topology *source) { int i; + int dest_index = 0; for (i = 0; i < ast_stream_topology_get_count(source); ++i) { struct ast_stream *clone; + int added = 0; clone = ast_stream_clone(ast_stream_topology_get_stream(source, i), NULL); if (!clone) { return -1; } - if (ast_stream_topology_append_stream(dest, clone) < 0) { + + /* If we can reuse an existing removed stream then do so */ + while (dest_index < ast_stream_topology_get_count(dest)) { + struct ast_stream *stream = ast_stream_topology_get_stream(dest, dest_index); + + dest_index++; + + if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) { + ast_stream_topology_set_stream(dest, dest_index - 1, clone); + added = 1; + break; + } + } + + /* If no removed stream exists that we took the place of append the stream */ + if (!added && ast_stream_topology_append_stream(dest, clone) < 0) { ast_stream_free(clone); return -1; } diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c index f009943ed..51b5dab5c 100644 --- a/channels/chan_pjsip.c +++ b/channels/chan_pjsip.c @@ -1596,6 +1596,7 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi * fully support other video codecs */ if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_vp8) != AST_FORMAT_CMP_NOT_EQUAL || + ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_vp9) != AST_FORMAT_CMP_NOT_EQUAL || (channel->session->endpoint->media.webrtc && ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_h264) != AST_FORMAT_CMP_NOT_EQUAL)) { /* FIXME Fake RTP write, this will be sent as an RTCP packet. Ideally the diff --git a/configs/samples/config_test.conf.sample b/configs/samples/config_test.conf.sample index 2fff45ece..b7cb21292 100644 --- a/configs/samples/config_test.conf.sample +++ b/configs/samples/config_test.conf.sample @@ -6,6 +6,10 @@ [global] intopt=-1 uintopt=1 +timelenopt1=1ms +timelenopt2=1s +timelenopt3=1m +timelenopt4=1h doubleopt=0.1 sockaddropt=1.2.3.4:1234 boolopt=true @@ -23,6 +27,10 @@ customopt=yes [item] intopt=-1 uintopt=1 +timelenopt1=1 +timelenopt2=1 +timelenopt3=1 +timelenopt4=1 doubleopt=0.1 sockaddropt=1.2.3.4:1234 boolopt=true @@ -1356,6 +1356,7 @@ infodir docdir oldincludedir includedir +runstatedir localstatedir sharedstatedir sysconfdir @@ -1540,6 +1541,7 @@ datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' @@ -1792,6 +1794,15 @@ do | -silent | --silent | --silen | --sile | --sil) silent=yes ;; + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ @@ -1929,7 +1940,7 @@ fi for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ - libdir localedir mandir + libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. @@ -2082,6 +2093,7 @@ Fine tuning of the installation directories: --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] @@ -14915,7 +14927,7 @@ else We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -14961,7 +14973,7 @@ else We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -14985,7 +14997,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -15030,7 +15042,7 @@ else We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -15054,7 +15066,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -31910,7 +31922,7 @@ if eval \${$as_ac_Lib+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS -LIBS="-lcpg ${pbxlibdir} -lcfg $LIBS" +LIBS="-lcpg ${pbxlibdir} -lcpg $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -31952,7 +31964,7 @@ fi # now check for the header. if test "${AST_COROSYNC_FOUND}" = "yes"; then - COROSYNC_LIB="${pbxlibdir} -lcpg -lcfg" + COROSYNC_LIB="${pbxlibdir} -lcpg -lcpg" # if --with-COROSYNC=DIR has been specified, use it. if test "x${COROSYNC_DIR}" != "x"; then COROSYNC_INCLUDE="-I${COROSYNC_DIR}/include" diff --git a/configure.ac b/configure.ac index ecbe87093..a2eaab4ad 100644 --- a/configure.ac +++ b/configure.ac @@ -2414,7 +2414,7 @@ fi AST_EXT_LIB_CHECK([CODEC2], [codec2], [codec2_create], [codec2/codec2.h]) -AST_EXT_LIB_CHECK([COROSYNC], [cpg], [cpg_join], [corosync/cpg.h], [-lcfg]) +AST_EXT_LIB_CHECK([COROSYNC], [cpg], [cpg_join], [corosync/cpg.h], [-lcpg]) AST_EXT_LIB_CHECK([COROSYNC_CFG_STATE_TRACK], [cfg], [corosync_cfg_state_track], [corosync/cfg.h], [-lcfg]) AST_EXT_LIB_CHECK([SPEEX], [speex], [speex_encode], [speex/speex.h], [-lm]) diff --git a/contrib/scripts/install_prereq b/contrib/scripts/install_prereq index fb240890b..d69f5527a 100755 --- a/contrib/scripts/install_prereq +++ b/contrib/scripts/install_prereq @@ -26,7 +26,7 @@ PACKAGES_DEBIAN="$PACKAGES_DEBIAN libncurses-dev libz-dev libssl-dev libxml2-dev PACKAGES_DEBIAN="$PACKAGES_DEBIAN libcurl-dev libspeex-dev libspeexdsp-dev libogg-dev libvorbis-dev libasound2-dev portaudio19-dev libcurl4-openssl-dev" PACKAGES_DEBIAN="$PACKAGES_DEBIAN libpq-dev unixodbc-dev libsqlite0-dev libmysqlclient15-dev libneon27-dev libgmime-dev libusb-dev liblua5.1-0-dev lua5.1" PACKAGES_DEBIAN="$PACKAGES_DEBIAN libopenh323-dev libvpb-dev libgtk2.0-dev libmysqlclient-dev libbluetooth-dev libradiusclient-ng-dev freetds-dev" -PACKAGES_DEBIAN="$PACKAGES_DEBIAN libsnmp-dev libiksemel-dev libcorosync-dev libnewt-dev libpopt-dev libical-dev libspandsp-dev libjack-dev" +PACKAGES_DEBIAN="$PACKAGES_DEBIAN libsnmp-dev libiksemel-dev libcorosync-dev libcpg-dev libcfg-dev libnewt-dev libpopt-dev libical-dev libspandsp-dev libjack-dev" PACKAGES_DEBIAN="$PACKAGES_DEBIAN libresample-dev libc-client-dev binutils-dev libsrtp-dev libgsm1-dev libedit-dev doxygen libjansson-dev libldap-dev" PACKAGES_DEBIAN="$PACKAGES_DEBIAN subversion git libxslt1-dev automake libsrtp-dev libncurses5-dev python-dev" PACKAGES_RH="automake bzip2 gcc gcc-c++ patch ncurses-devel openssl-devel libxml2-devel unixODBC-devel libcurl-devel libogg-devel libvorbis-devel speex-devel" diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 55126b472..f0fe5b212 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -2239,11 +2239,12 @@ int ast_waitfordigit(struct ast_channel *c, int ms); * Same as ast_waitfordigit() with audio fd for outputting read audio and ctrlfd to monitor for reading. * \param c channel to wait for a digit on * \param ms how many milliseconds to wait (<0 for indefinite). + * \param breakon string of DTMF digits to break upon or NULL for any. * \param audiofd audio file descriptor to write to if audio frames are received * \param ctrlfd control file descriptor to monitor for reading * \return Returns 1 if ctrlfd becomes available */ -int ast_waitfordigit_full(struct ast_channel *c, int ms, int audiofd, int ctrlfd); +int ast_waitfordigit_full(struct ast_channel *c, int ms, const char *breakon, int audiofd, int ctrlfd); /*! * \brief Reads multiple digits diff --git a/include/asterisk/config.h b/include/asterisk/config.h index f57966b0b..1addfa317 100644 --- a/include/asterisk/config.h +++ b/include/asterisk/config.h @@ -1086,6 +1086,11 @@ enum ast_parse_flags { PARSE_UINT16 = 0x0005, #endif + /* Returns an int processed by ast_app_parse_timelen. + * The first argument is an enum ast_timelen value (required). + */ + PARSE_TIMELEN = 0x0006, + /* Returns a struct ast_sockaddr, with optional default value * (passed by reference) and port handling (accept, ignore, * require, forbid). The format is 'ipaddress[:port]'. IPv6 address @@ -1152,6 +1157,12 @@ enum ast_parse_flags { * returns 1, b unchanged * ast_parse_arg("12", PARSE_UINT32|PARSE_IN_RANGE|PARSE_RANGE_DEFAULTS, &a, 1, 10); * returns 1, a = 10 + * ast_parse_arg("223", PARSE_TIMELEN|PARSE_IN_RANGE, &a, TIMELEN_SECONDS, -1000, 1000); + * returns 0, a = 1000 + * ast_parse_arg("223", PARSE_TIMELEN|PARSE_IN_RANGE, &a, TIMELEN_SECONDS, -1000, 250000); + * returns 0, a = 223000 + * ast_parse_arg("223", PARSE_TIMELEN|PARSE_IN_RANGE|PARSE_DEFAULT, &a, TIMELEN_SECONDS, 9999, -1000, 250000); + * returns 0, a = 9999 * ast_parse_arg("www.foo.biz:44", PARSE_INADDR, &sa); * returns 0, sa contains address and port * ast_parse_arg("www.foo.biz", PARSE_INADDR|PARSE_PORT_REQUIRE, &sa); diff --git a/include/asterisk/config_options.h b/include/asterisk/config_options.h index f2a457eb5..f4c3db188 100644 --- a/include/asterisk/config_options.h +++ b/include/asterisk/config_options.h @@ -468,6 +468,30 @@ enum aco_option_type { */ OPT_YESNO_T, + /*! \brief Type for default option handler for time length signed integers + * + * \note aco_option_register flags: + * See flags available for use with the PARSE_TIMELEN type for the ast_parse_arg function + * aco_option_register varargs: + * FLDSET macro with the field of type int + * The remaining varargs for should be arguments compatible with the varargs for the + * ast_parse_arg function with the PARSE_TIMELEN type and the flags passed in the + * aco_option_register flags parameter. + * + * \note In most situations, it is preferable to not pass the PARSE_DEFAULT flag. If a config + * contains an invalid value, it is better to let the config loading fail with warnings so that + * the problem is fixed by the administrator. + * + * Example: + * struct test_item { + * int timelen; + * }; + * {code} + * aco_option_register(&cfg_info, "timelen", ACO_EXACT, my_types, "3", OPT_TIMELEN_T, PARSE_IN_RANGE, FLDSET(struct test_item, intopt), TIMELEN_MILLISECONDS, -10, 10); + * {endcode} + */ + OPT_TIMELEN_T, + }; /*! \brief A callback function for handling a particular option diff --git a/include/asterisk/format.h b/include/asterisk/format.h index 0bad96dcc..946c03d98 100644 --- a/include/asterisk/format.h +++ b/include/asterisk/format.h @@ -32,7 +32,7 @@ struct ast_format; /*! \brief Format comparison results */ enum ast_format_cmp_res { - /*! Both formats are equivalent to eachother */ + /*! Both formats are equivalent to each other */ AST_FORMAT_CMP_EQUAL = 0, /*! Both formats are completely different and not the same in any way */ AST_FORMAT_CMP_NOT_EQUAL, @@ -110,7 +110,7 @@ struct ast_format_interface { struct ast_format *(* const format_parse_sdp_fmtp)(const struct ast_format *format, const char *attributes); /*! - * \brief Generate SDP attribute information from an ast_format_attr structure. + * \brief Generate SDP attribute information from an ast_format structure. * * \param format The format containing attributes * \param payload The payload number to place into the fmtp line diff --git a/include/asterisk/format_cache.h b/include/asterisk/format_cache.h index 92272e8eb..067546310 100644 --- a/include/asterisk/format_cache.h +++ b/include/asterisk/format_cache.h @@ -184,6 +184,11 @@ extern struct ast_format *ast_format_mp4; extern struct ast_format *ast_format_vp8; /*! + * \brief Built-in cached vp9 format. + */ +extern struct ast_format *ast_format_vp9; + +/*! * \brief Built-in cached jpeg format. */ extern struct ast_format *ast_format_jpeg; diff --git a/include/asterisk/manager.h b/include/asterisk/manager.h index 3de7b1d17..67dcead47 100644 --- a/include/asterisk/manager.h +++ b/include/asterisk/manager.h @@ -54,7 +54,7 @@ - \ref manager.c Main manager code file */ -#define AMI_VERSION "3.2.0" +#define AMI_VERSION "4.0.0" #define DEFAULT_MANAGER_PORT 5038 /* Default port for Asterisk management via TCP */ #define DEFAULT_MANAGER_TLS_PORT 5039 /* Default port for Asterisk management via TCP */ diff --git a/include/asterisk/sdp.h b/include/asterisk/sdp.h index 224a0e5a3..768469544 100644 --- a/include/asterisk/sdp.h +++ b/include/asterisk/sdp.h @@ -254,16 +254,6 @@ void ast_sdp_s_free(struct ast_sdp_s_line *s_line); void ast_sdp_t_free(struct ast_sdp_t_line *t_line); /*! - * \brief Free an SDP - * Frees the sdp and all resources it contains - * - * \param sdp The sdp to free - * - * \since 15 - */ -void ast_sdp_free(struct ast_sdp *sdp); - -/*! * \brief Allocate an SDP Attribute * * \param name Attribute Name @@ -544,7 +534,7 @@ int ast_sdp_m_add_format(struct ast_sdp_m_line *m_line, const struct ast_sdp_opt int rtp_code, int asterisk_format, const struct ast_format *format, int code); /*! - * \brief Create an SDP + * \brief Create an SDP ao2 object * * \param o_line Origin * \param c_line Connection diff --git a/include/asterisk/sdp_options.h b/include/asterisk/sdp_options.h index b8c1bbd56..e45ae8cb1 100644 --- a/include/asterisk/sdp_options.h +++ b/include/asterisk/sdp_options.h @@ -20,6 +20,7 @@ #define _ASTERISK_SDP_OPTIONS_H #include "asterisk/udptl.h" +#include "asterisk/format_cap.h" struct ast_sdp_options; @@ -80,6 +81,149 @@ enum ast_sdp_options_encryption { }; /*! + * \brief Callback when processing an offer SDP for our answer SDP. + * \since 15.0.0 + * + * \details + * This callback is called after merging our last negotiated topology + * with the remote's offer topology and before we have sent our answer + * SDP. At this point you can alter new_topology streams. You can + * decline, remove formats, or rename streams. Changing anything else + * on the streams is likely to not end well. + * + * * To decline a stream simply set the stream state to + * AST_STREAM_STATE_REMOVED. You could implement a maximum number + * of active streams of a given type policy. + * + * * To remove formats use the format API to remove any formats from a + * stream. The streams have the current joint negotiated formats. + * Most likely you would want to remove all but the first format. + * + * * To rename a stream you need to clone the stream and give it a + * new name and then set it in new_topology using + * ast_stream_topology_set_stream(). + * + * \note Removing all formats is an error. You should decline the + * stream instead. + * + * \param context User supplied context data pointer for the SDP + * state. + * \param old_topology Active negotiated topology. NULL if this is + * the first SDP negotiation. The old topology is available so you + * can tell if any streams are new or changing type. + * \param new_topology New negotiated topology that we intend to + * generate the answer SDP. + * + * \return Nothing + */ +typedef void (*ast_sdp_answerer_modify_cb)(void *context, + const struct ast_stream_topology *old_topology, + struct ast_stream_topology *new_topology); + +/*! + * \internal + * \brief Callback when generating a topology for our SDP offer. + * \since 15.0.0 + * + * \details + * This callback is called after merging any topology updates from the + * system by ast_sdp_state_update_local_topology() and before we have + * sent our offer SDP. At this point you can alter new_topology + * streams. You can decline, add/remove/update formats, or rename + * streams. Changing anything else on the streams is likely to not + * end well. + * + * * To decline a stream simply set the stream state to + * AST_STREAM_STATE_REMOVED. You could implement a maximum number + * of active streams of a given type policy. + * + * * To update formats use the format API to change formats of the + * streams. The streams have the current proposed formats. You + * could do whatever you want for formats but you should stay within + * the configured formats for the stream type's endpoint. However, + * you should use ast_sdp_state_update_local_topology() instead of + * this backdoor method. + * + * * To rename a stream you need to clone the stream and give it a + * new name and then set it in new_topology using + * ast_stream_topology_set_stream(). + * + * \note Removing all formats is an error. You should decline the + * stream instead. + * + * \note Declined new streams that are in slots higher than present in + * old_topology are removed so the SDP can be smaller. The remote has + * never seen those slots so we shouldn't bother keeping them. + * + * \param context User supplied context data pointer for the SDP + * state. + * \param old_topology Active negotiated topology. NULL if this is + * the first SDP negotiation. The old topology is available so you + * can tell if any streams are new or changing type. + * \param new_topology Merged topology that we intend to generate the + * offer SDP. + * + * \return Nothing + */ +typedef void (*ast_sdp_offerer_modify_cb)(void *context, + const struct ast_stream_topology *old_topology, + struct ast_stream_topology *new_topology); + +/*! + * \brief Callback when generating an offer SDP to configure extra stream data. + * \since 15.0.0 + * + * \details + * This callback is called after any ast_sdp_offerer_modify_cb + * callback and before we have sent our offer SDP. The callback can + * call several SDP API calls to configure the proposed capabilities + * of streams before we create the SDP offer. For example, the + * callback could configure a stream specific connection address, T.38 + * parameters, RTP instance, or UDPTL instance parameters. + * + * \param context User supplied context data pointer for the SDP + * state. + * \param topology Topology ready to configure extra stream options. + * + * \return Nothing + */ +typedef void (*ast_sdp_offerer_config_cb)(void *context, const struct ast_stream_topology *topology); + +/*! + * \brief Callback before applying a topology. + * \since 15.0.0 + * + * \details + * This callback is called before the topology is applied so the + * using module can do what is necessary before the topology becomes + * active. + * + * \param context User supplied context data pointer for the SDP + * state. + * \param topology Topology ready to be applied. + * + * \return Nothing + */ +typedef void (*ast_sdp_preapply_cb)(void *context, const struct ast_stream_topology *topology); + +/*! + * \brief Callback after applying a topology. + * \since 15.0.0 + * + * \details + * This callback is called after the topology is applied so the + * using module can do what is necessary after the topology becomes + * active. + * + * \param context User supplied context data pointer for the SDP + * state. + * \param topology Topology already applied. + * + * \return Nothing + */ +typedef void (*ast_sdp_postapply_cb)(void *context, const struct ast_stream_topology *topology); + +/*! * \since 15.0.0 * \brief Allocate a new SDP options structure. * @@ -204,6 +348,24 @@ void ast_sdp_options_set_rtp_engine(struct ast_sdp_options *options, */ const char *ast_sdp_options_get_rtp_engine(const struct ast_sdp_options *options); +void ast_sdp_options_set_state_context(struct ast_sdp_options *options, void *state_context); +void *ast_sdp_options_get_state_context(const struct ast_sdp_options *options); + +void ast_sdp_options_set_answerer_modify_cb(struct ast_sdp_options *options, ast_sdp_answerer_modify_cb answerer_modify_cb); +ast_sdp_answerer_modify_cb ast_sdp_options_get_answerer_modify_cb(const struct ast_sdp_options *options); + +void ast_sdp_options_set_offerer_modify_cb(struct ast_sdp_options *options, ast_sdp_offerer_modify_cb offerer_modify_cb); +ast_sdp_offerer_modify_cb ast_sdp_options_get_offerer_modify_cb(const struct ast_sdp_options *options); + +void ast_sdp_options_set_offerer_config_cb(struct ast_sdp_options *options, ast_sdp_offerer_config_cb offerer_config_cb); +ast_sdp_offerer_config_cb ast_sdp_options_get_offerer_config_cb(const struct ast_sdp_options *options); + +void ast_sdp_options_set_preapply_cb(struct ast_sdp_options *options, ast_sdp_preapply_cb preapply_cb); +ast_sdp_preapply_cb ast_sdp_options_get_preapply_cb(const struct ast_sdp_options *options); + +void ast_sdp_options_set_postapply_cb(struct ast_sdp_options *options, ast_sdp_postapply_cb postapply_cb); +ast_sdp_postapply_cb ast_sdp_options_get_postapply_cb(const struct ast_sdp_options *options); + /*! * \since 15.0.0 * \brief Set SDP Options rtp_symmetric @@ -505,6 +667,26 @@ unsigned int ast_sdp_options_get_udptl_far_max_datagram(const struct ast_sdp_opt /*! * \since 15.0.0 + * \brief Set SDP Options max_streams + * + * \param options SDP Options + * \param max_streams + */ +void ast_sdp_options_set_max_streams(struct ast_sdp_options *options, + unsigned int max_streams); + +/*! + * \since 15.0.0 + * \brief Get SDP Options max_streams + * + * \param options SDP Options + * + * \returns max_streams + */ +unsigned int ast_sdp_options_get_max_streams(const struct ast_sdp_options *options); + +/*! + * \since 15.0.0 * \brief Enable setting SSRC level attributes on SDPs * * \param options SDP Options @@ -547,4 +729,46 @@ void ast_sdp_options_set_sched_type(struct ast_sdp_options *options, struct ast_sched_context *ast_sdp_options_get_sched_type(const struct ast_sdp_options *options, enum ast_media_type type); +/*! + * \brief Set all allowed stream types to create new streams. + * \since 15.0.0 + * + * \param options SDP Options + * \param cap Format capabilities to set all allowed stream types at once. + * Could be NULL to disable creating any new streams. + * + * \return Nothing + */ +void ast_sdp_options_set_format_caps(struct ast_sdp_options *options, + struct ast_format_cap *cap); + +/*! + * \brief Set the SDP options format cap used to create new streams of the type. + * \since 15.0.0 + * + * \param options SDP Options + * \param type Media type the format cap represents. + * \param cap Format capabilities to use for the specified media type. + * Could be NULL to disable creating new streams of type. + * + * \return Nothing + */ +void ast_sdp_options_set_format_cap_type(struct ast_sdp_options *options, + enum ast_media_type type, struct ast_format_cap *cap); + +/*! + * \brief Get the SDP options format cap used to create new streams of the type. + * \since 15.0.0 + * + * \param options SDP Options + * \param type Media type the format cap represents. + * + * \retval NULL if stream not allowed to be created. + * \retval cap to use in negotiating the new stream. + * + * \note The returned cap does not have its own ao2 ref. + */ +struct ast_format_cap *ast_sdp_options_get_format_cap_type(const struct ast_sdp_options *options, + enum ast_media_type type); + #endif /* _ASTERISK_SDP_OPTIONS_H */ diff --git a/include/asterisk/sdp_state.h b/include/asterisk/sdp_state.h index b8209e1d5..ec9d502e2 100644 --- a/include/asterisk/sdp_state.h +++ b/include/asterisk/sdp_state.h @@ -30,12 +30,22 @@ struct ast_control_t38_parameters; /*! * \brief Allocate a new SDP state * + * \details * SDP state keeps tabs on everything SDP-related for a media session. * Most SDP operations will require the state to be provided. * Ownership of the SDP options is taken on by the SDP state. * A good strategy is to call this during session creation. + * + * \param topology Initial stream topology to offer. + * NULL if we are going to be the answerer. We can always + * update the local topology later if it turns out we need + * to be the offerer. + * \param options SDP options for the duration of the session. + * + * \retval SDP state struct + * \retval NULL on failure */ -struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *streams, +struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *topology, struct ast_sdp_options *options); /*! @@ -86,6 +96,8 @@ int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_ * * If this is called prior to receiving a remote SDP, then this will just mirror * the local configured endpoint capabilities. + * + * \note Cannot return NULL. It is a BUG if it does. */ const struct ast_stream_topology *ast_sdp_state_get_joint_topology( const struct ast_sdp_state *sdp_state); @@ -93,6 +105,7 @@ const struct ast_stream_topology *ast_sdp_state_get_joint_topology( /*! * \brief Get the local topology * + * \note Cannot return NULL. It is a BUG if it does. */ const struct ast_stream_topology *ast_sdp_state_get_local_topology( const struct ast_sdp_state *sdp_state); @@ -114,9 +127,10 @@ const struct ast_sdp_options *ast_sdp_state_get_options( * \retval NULL Failure * * \note - * This function will allocate a new SDP with RTP instances if it has not already - * been allocated. - * + * This function will return the last local SDP created if one were + * previously requested for the current negotiation. Otherwise it + * creates our SDP offer/answer depending on what role we are playing + * in the current negotiation. */ const struct ast_sdp *ast_sdp_state_get_local_sdp(struct ast_sdp_state *sdp_state); @@ -152,6 +166,7 @@ const void *ast_sdp_state_get_local_sdp_impl(struct ast_sdp_state *sdp_state); * * \retval 0 Success * \retval non-0 Failure + * Use ast_sdp_state_is_offer_rejected() to see if the SDP offer was rejected. * * \since 15 */ @@ -165,39 +180,72 @@ int ast_sdp_state_set_remote_sdp(struct ast_sdp_state *sdp_state, const struct a * * \retval 0 Success * \retval non-0 Failure + * Use ast_sdp_state_is_offer_rejected() to see if the SDP offer was rejected. * * \since 15 */ int ast_sdp_state_set_remote_sdp_from_impl(struct ast_sdp_state *sdp_state, const void *remote); /*! - * \brief Reset the SDP state and stream capabilities as if the SDP state had just been allocated. + * \brief Was the set remote offer rejected. + * \since 15.0.0 * * \param sdp_state - * \param remote The implementation's representation of an SDP. * - * \retval 0 Success + * \retval 0 if not rejected. + * \retval non-zero if rejected. + */ +int ast_sdp_state_is_offer_rejected(struct ast_sdp_state *sdp_state); + +/*! + * \brief Are we the SDP offerer. + * \since 15.0.0 * - * \note - * This is most useful for when a channel driver is sending a session refresh message - * and needs to re-advertise its initial capabilities instead of the previously-negotiated - * joint capabilities. + * \param sdp_state * - * \since 15 + * \retval 0 if we are not the offerer. + * \retval non-zero we are the offerer. */ -int ast_sdp_state_reset(struct ast_sdp_state *sdp_state); +int ast_sdp_state_is_offerer(struct ast_sdp_state *sdp_state); + +/*! + * \brief Are we the SDP answerer. + * \since 15.0.0 + * + * \param sdp_state + * + * \retval 0 if we are not the answerer. + * \retval non-zero we are the answerer. + */ +int ast_sdp_state_is_answerer(struct ast_sdp_state *sdp_state); + +/*! + * \brief Restart the SDP offer/answer negotiations. + * + * \param sdp_state + * + * \retval 0 Success + * \retval non-0 Failure + */ +int ast_sdp_state_restart_negotiations(struct ast_sdp_state *sdp_state); /*! * \brief Update the local stream topology on the SDP state. * + * \details + * Basically we are saving off any topology updates until we create the + * next SDP offer. Repeated updates merge with the previous updated + * topology. + * * \param sdp_state - * \param streams The new stream topology. + * \param topology The new stream topology. * * \retval 0 Success + * \retval non-0 Failure * * \since 15 */ -int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *streams); +int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *topology); /*! * \brief Set the local address (IP address) to use for connection addresses @@ -231,7 +279,26 @@ int ast_sdp_state_set_connection_address(struct ast_sdp_state *sdp_state, int st /*! * \since 15.0.0 - * \brief Set a stream to be held or unheld + * \brief Set the global locally held state. + * + * \param sdp_state + * \param locally_held + */ +void ast_sdp_state_set_global_locally_held(struct ast_sdp_state *sdp_state, unsigned int locally_held); + +/*! + * \since 15.0.0 + * \brief Get the global locally held state. + * + * \param sdp_state + * + * \returns locally_held + */ +unsigned int ast_sdp_state_get_global_locally_held(const struct ast_sdp_state *sdp_state); + +/*! + * \since 15.0.0 + * \brief Set a stream to be held or unheld locally * * \param sdp_state * \param stream_index The stream to set the held value for @@ -242,25 +309,37 @@ void ast_sdp_state_set_locally_held(struct ast_sdp_state *sdp_state, /*! * \since 15.0.0 - * \brief Set the UDPTL session parameters + * \brief Get whether a stream is locally held or not * * \param sdp_state - * \param stream_index The stream to set the UDPTL session parameters for - * \param params + * \param stream_index The stream to get the held state for + * + * \returns locally_held */ -void ast_sdp_state_set_t38_parameters(struct ast_sdp_state *sdp_state, - int stream_index, struct ast_control_t38_parameters *params); +unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_state, + int stream_index); /*! * \since 15.0.0 - * \brief Get whether a stream is held or not + * \brief Get whether a stream is remotely held or not * * \param sdp_state * \param stream_index The stream to get the held state for * - * \returns locally_held + * \returns remotely_held */ -unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_state, +unsigned int ast_sdp_state_get_remotely_held(const struct ast_sdp_state *sdp_state, int stream_index); +/*! + * \since 15.0.0 + * \brief Set the UDPTL session parameters + * + * \param sdp_state + * \param stream_index The stream to set the UDPTL session parameters for + * \param params + */ +void ast_sdp_state_set_t38_parameters(struct ast_sdp_state *sdp_state, + int stream_index, struct ast_control_t38_parameters *params); + #endif /* _ASTERISK_SDP_STATE_H */ diff --git a/include/asterisk/stream.h b/include/asterisk/stream.h index 4027231ed..2c1053a7b 100644 --- a/include/asterisk/stream.h +++ b/include/asterisk/stream.h @@ -228,6 +228,17 @@ void ast_stream_set_state(struct ast_stream *stream, enum ast_stream_state state const char *ast_stream_state2str(enum ast_stream_state state); /*! + * \brief Convert a string to a stream state + * + * \param str The string to convert + * + * \return The stream state + * + * \since 15.0.0 + */ +enum ast_stream_state ast_stream_str2state(const char *str); + +/*! * \brief Get the opaque stream data * * \param stream The media stream diff --git a/main/app.c b/main/app.c index 1eb0741ee..69c96c06c 100644 --- a/main/app.c +++ b/main/app.c @@ -3060,19 +3060,32 @@ int ast_app_parse_timelen(const char *timestr, int *result, enum ast_timelen uni case 'h': case 'H': unit = TIMELEN_HOURS; + if (u[1] != '\0') { + return -1; + } break; case 's': case 'S': unit = TIMELEN_SECONDS; + if (u[1] != '\0') { + return -1; + } break; case 'm': case 'M': if (toupper(u[1]) == 'S') { unit = TIMELEN_MILLISECONDS; + if (u[2] != '\0') { + return -1; + } } else if (u[1] == '\0') { unit = TIMELEN_MINUTES; + } else { + return -1; } break; + default: + return -1; } } diff --git a/main/channel.c b/main/channel.c index 23bb74f08..66825559c 100644 --- a/main/channel.c +++ b/main/channel.c @@ -3160,7 +3160,7 @@ int ast_waitfor(struct ast_channel *c, int ms) int ast_waitfordigit(struct ast_channel *c, int ms) { - return ast_waitfordigit_full(c, ms, -1, -1); + return ast_waitfordigit_full(c, ms, NULL, -1, -1); } int ast_settimeout(struct ast_channel *c, unsigned int rate, int (*func)(const void *data), void *data) @@ -3222,7 +3222,7 @@ int ast_settimeout_full(struct ast_channel *c, unsigned int rate, int (*func)(co return res; } -int ast_waitfordigit_full(struct ast_channel *c, int timeout_ms, int audiofd, int cmdfd) +int ast_waitfordigit_full(struct ast_channel *c, int timeout_ms, const char *breakon, int audiofd, int cmdfd) { struct timeval start = ast_tvnow(); int ms; @@ -3273,9 +3273,12 @@ int ast_waitfordigit_full(struct ast_channel *c, int timeout_ms, int audiofd, in break; case AST_FRAME_DTMF_END: res = f->subclass.integer; - ast_frfree(f); - ast_channel_clear_flag(c, AST_FLAG_END_DTMF_ONLY); - return res; + if (!breakon || strchr(breakon, res)) { + ast_frfree(f); + ast_channel_clear_flag(c, AST_FLAG_END_DTMF_ONLY); + return res; + } + break; case AST_FRAME_CONTROL: switch (f->subclass.integer) { case AST_CONTROL_HANGUP: @@ -6356,11 +6359,11 @@ int ast_readstring_full(struct ast_channel *c, char *s, int len, int timeout, in silgen = ast_channel_start_silence_generator(c); usleep(1000); if (!d) - d = ast_waitfordigit_full(c, to, audiofd, ctrlfd); + d = ast_waitfordigit_full(c, to, NULL, audiofd, ctrlfd); } else { if (!silgen && ast_opt_transmit_silence) silgen = ast_channel_start_silence_generator(c); - d = ast_waitfordigit_full(c, to, audiofd, ctrlfd); + d = ast_waitfordigit_full(c, to, NULL, audiofd, ctrlfd); } if (d < 0) { ast_channel_stop_silence_generator(c, silgen); diff --git a/main/codec_builtin.c b/main/codec_builtin.c index 32ec12d3d..25aa51384 100644 --- a/main/codec_builtin.c +++ b/main/codec_builtin.c @@ -820,6 +820,13 @@ static struct ast_codec vp8 = { .sample_rate = 1000, }; +static struct ast_codec vp9 = { + .name = "vp9", + .description = "VP9 video", + .type = AST_MEDIA_TYPE_VIDEO, + .sample_rate = 1000, +}; + static struct ast_codec t140red = { .name = "red", .description = "T.140 Realtime Text with redundancy", @@ -966,6 +973,7 @@ int ast_codec_builtin_init(void) res |= CODEC_REGISTER_AND_CACHE(h264); res |= CODEC_REGISTER_AND_CACHE(mpeg4); res |= CODEC_REGISTER_AND_CACHE(vp8); + res |= CODEC_REGISTER_AND_CACHE(vp9); res |= CODEC_REGISTER_AND_CACHE(t140red); res |= CODEC_REGISTER_AND_CACHE(t140); res |= CODEC_REGISTER_AND_CACHE(t38); diff --git a/main/config.c b/main/config.c index a3e09f67e..3d8dcfb3d 100644 --- a/main/config.c +++ b/main/config.c @@ -3741,6 +3741,55 @@ uint32_done: break; } + case PARSE_TIMELEN: + { + int x = 0; + int *result = p_result; + int def = result ? *result : 0; + int high = INT_MAX; + int low = INT_MIN; + enum ast_timelen defunit; + + defunit = va_arg(ap, enum ast_timelen); + /* optional arguments: default value and/or (low, high) */ + if (flags & PARSE_DEFAULT) { + def = va_arg(ap, int); + } + if (flags & (PARSE_IN_RANGE | PARSE_OUT_RANGE)) { + low = va_arg(ap, int); + high = va_arg(ap, int); + } + if (ast_strlen_zero(arg)) { + error = 1; + goto timelen_done; + } + error = ast_app_parse_timelen(arg, &x, defunit); + if (error || x < INT_MIN || x > INT_MAX) { + /* Parse error, or type out of int bounds */ + error = 1; + goto timelen_done; + } + error = (x < low) || (x > high); + if (flags & PARSE_RANGE_DEFAULTS) { + if (x < low) { + def = low; + } else if (x > high) { + def = high; + } + } + if (flags & PARSE_OUT_RANGE) { + error = !error; + } +timelen_done: + if (result) { + *result = error ? def : x; + } + + ast_debug(3, "extract timelen from [%s] in [%d, %d] gives [%d](%d)\n", + arg, low, high, result ? *result : x, error); + break; + } + case PARSE_DOUBLE: { double *result = p_result; diff --git a/main/config_options.c b/main/config_options.c index c80777906..8eacbda35 100644 --- a/main/config_options.c +++ b/main/config_options.c @@ -34,6 +34,7 @@ #include "asterisk/config_options.h" #include "asterisk/stringfields.h" #include "asterisk/acl.h" +#include "asterisk/app.h" #include "asterisk/frame.h" #include "asterisk/xmldoc.h" #include "asterisk/cli.h" @@ -118,6 +119,7 @@ static void config_option_destroy(void *obj) static int int_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj); static int uint_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj); +static int timelen_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj); static int double_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj); static int sockaddr_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj); static int stringfield_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj); @@ -151,6 +153,7 @@ static aco_option_handler ast_config_option_default_handler(enum aco_option_type case OPT_SOCKADDR_T: return sockaddr_handler_fn; case OPT_STRINGFIELD_T: return stringfield_handler_fn; case OPT_UINT_T: return uint_handler_fn; + case OPT_TIMELEN_T: return timelen_handler_fn; case OPT_CUSTOM_T: return NULL; } @@ -1378,6 +1381,39 @@ static int uint_handler_fn(const struct aco_option *opt, struct ast_variable *va return res; } +/*! \brief Default option handler for timelen signed integers + * \note For a description of the opt->flags and opt->args values, see the documentation for + * enum aco_option_type in config_options.h + */ +static int timelen_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + int *field = (int *)(obj + opt->args[0]); + unsigned int flags = PARSE_TIMELEN | opt->flags; + int res = 0; + if (opt->flags & PARSE_IN_RANGE) { + if (opt->flags & PARSE_DEFAULT) { + res = ast_parse_arg(var->value, flags, field, (enum ast_timelen) opt->args[1], (int) opt->args[2], (int) opt->args[3], opt->args[4]); + } else { + res = ast_parse_arg(var->value, flags, field, (enum ast_timelen) opt->args[1], (int) opt->args[2], (int) opt->args[3]); + } + if (res) { + if (opt->flags & PARSE_RANGE_DEFAULTS) { + ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[2], (int) opt->args[3]); + res = 0; + } else if (opt->flags & PARSE_DEFAULT) { + ast_log(LOG_WARNING, "Failed to set %s=%s, Set to default value %d instead.\n", var->name, var->value, *field); + res = 0; + } + } + } else if ((opt->flags & PARSE_DEFAULT) && ast_parse_arg(var->value, flags, field, (enum ast_timelen) opt->args[1], (int) opt->args[2])) { + ast_log(LOG_WARNING, "Attempted to set %s=%s, but set it to %d instead due to default)\n", var->name, var->value, *field); + } else { + res = ast_parse_arg(var->value, flags, field, (enum ast_timelen) opt->args[1]); + } + + return res; +} + /*! \brief Default option handler for doubles * \note For a description of the opt->flags and opt->args values, see the documentation for * enum aco_option_type in config_options.h diff --git a/main/format_cache.c b/main/format_cache.c index 302bbf827..1a67ebe60 100644 --- a/main/format_cache.c +++ b/main/format_cache.c @@ -191,6 +191,11 @@ struct ast_format *ast_format_mp4; struct ast_format *ast_format_vp8; /*! + * \brief Built-in cached vp9 format. + */ +struct ast_format *ast_format_vp9; + +/*! * \brief Built-in cached jpeg format. */ struct ast_format *ast_format_jpeg; @@ -345,6 +350,7 @@ static void format_cache_shutdown(void) ao2_replace(ast_format_h264, NULL); ao2_replace(ast_format_mp4, NULL); ao2_replace(ast_format_vp8, NULL); + ao2_replace(ast_format_vp9, NULL); ao2_replace(ast_format_t140_red, NULL); ao2_replace(ast_format_t140, NULL); ao2_replace(ast_format_t38, NULL); @@ -444,6 +450,8 @@ static void set_cached_format(const char *name, struct ast_format *format) ao2_replace(ast_format_mp4, format); } else if (!strcmp(name, "vp8")) { ao2_replace(ast_format_vp8, format); + } else if (!strcmp(name, "vp9")) { + ao2_replace(ast_format_vp9, format); } else if (!strcmp(name, "red")) { ao2_replace(ast_format_t140_red, format); } else if (!strcmp(name, "t140")) { diff --git a/main/pbx_builtins.c b/main/pbx_builtins.c index bc27b0d58..9d43c10ff 100644 --- a/main/pbx_builtins.c +++ b/main/pbx_builtins.c @@ -580,6 +580,42 @@ <para>This application waits for a specified number of <replaceable>seconds</replaceable>.</para> </description> </application> + <application name="WaitDigit" language="en_US"> + <synopsis> + Waits for a digit to be entered. + </synopsis> + <syntax> + <parameter name="seconds"> + <para>Can be passed with fractions of a second. For example, <literal>1.5</literal> will ask the + application to wait for 1.5 seconds.</para> + </parameter> + <parameter name="digits"> + <para>Digits to accept, all others are ignored.</para> + </parameter> + </syntax> + <description> + <para>This application waits for the user to press one of the accepted + <replaceable>digits</replaceable> for a specified number of + <replaceable>seconds</replaceable>.</para> + <variablelist> + <variable name="WAITDIGITSTATUS"> + <para>This is the final status of the command</para> + <value name="ERROR">Parameters are invalid.</value> + <value name="DTMF">An accepted digit was received.</value> + <value name="TIMEOUT">The timeout passed before any acceptable digits were received.</value> + <value name="CANCEL">The channel has hungup or was redirected.</value> + </variable> + <variable name="WAITDIGITRESULT"> + <para>The digit that was received, only set if + <variable>WAITDIGITSTATUS</variable> is <literal>DTMF</literal>.</para> + </variable> + </variablelist> + </description> + <see-also> + <ref type="application">Wait</ref> + <ref type="application">WaitExten</ref> + </see-also> + </application> <application name="WaitExten" language="en_US"> <synopsis> Waits for an extension to be entered. @@ -957,6 +993,47 @@ static int pbx_builtin_wait(struct ast_channel *chan, const char *data) /*! * \ingroup applications */ +static int pbx_builtin_waitdigit(struct ast_channel *chan, const char *data) +{ + int res; + int ms; + char *parse; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(timeout); + AST_APP_ARG(digits); + ); + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_app_parse_timelen(args.timeout, &ms, TIMELEN_SECONDS) || ms < 0) { + pbx_builtin_setvar_helper(chan, "WAITDIGITSTATUS", "ERROR"); + return 0; + } + + /* Wait for "n" seconds */ + res = ast_waitfordigit_full(chan, ms, S_OR(args.digits, AST_DIGIT_ANY), -1, -1); + if (res < 0) { + pbx_builtin_setvar_helper(chan, "WAITDIGITSTATUS", "CANCEL"); + return -1; + } + + if (res == 0) { + pbx_builtin_setvar_helper(chan, "WAITDIGITSTATUS", "TIMEOUT"); + } else { + char key[2]; + + snprintf(key, sizeof(key), "%c", res); + pbx_builtin_setvar_helper(chan, "WAITDIGITRESULT", key); + pbx_builtin_setvar_helper(chan, "WAITDIGITSTATUS", "DTMF"); + } + + return 0; +} + +/*! + * \ingroup applications + */ static int pbx_builtin_waitexten(struct ast_channel *chan, const char *data) { int ms, res; @@ -1410,6 +1487,7 @@ struct pbx_builtin { { "SayPhonetic", pbx_builtin_sayphonetic }, { "SetAMAFlags", pbx_builtin_setamaflags }, { "Wait", pbx_builtin_wait }, + { "WaitDigit", pbx_builtin_waitdigit }, { "WaitExten", pbx_builtin_waitexten } }; diff --git a/main/rtp_engine.c b/main/rtp_engine.c index 64b2b317b..e078b2400 100644 --- a/main/rtp_engine.c +++ b/main/rtp_engine.c @@ -3268,9 +3268,10 @@ int ast_rtp_engine_init(void) set_next_mime_type(ast_format_siren7, 0, "audio", "G7221", 16000); set_next_mime_type(ast_format_siren14, 0, "audio", "G7221", 32000); set_next_mime_type(ast_format_g719, 0, "audio", "G719", 48000); - /* Opus and VP8 */ + /* Opus, VP8, and VP9 */ set_next_mime_type(ast_format_opus, 0, "audio", "opus", 48000); set_next_mime_type(ast_format_vp8, 0, "video", "VP8", 90000); + set_next_mime_type(ast_format_vp9, 0, "video", "VP9", 90000); /* Define the static rtp payload mappings */ add_static_payload(0, ast_format_ulaw, 0); @@ -3311,6 +3312,7 @@ int ast_rtp_engine_init(void) add_static_payload(105, ast_format_t140_red, 0); /* Real time text chat (with redundancy encoding) */ add_static_payload(106, ast_format_t140, 0); /* Real time text chat */ add_static_payload(107, ast_format_opus, 0); + add_static_payload(108, ast_format_vp9, 0); add_static_payload(110, ast_format_speex, 0); add_static_payload(111, ast_format_g726, 0); @@ -3413,4 +3415,4 @@ void ast_rtp_instance_set_stream_num(struct ast_rtp_instance *rtp, int stream_nu rtp->engine->set_stream_num(rtp, stream_num); } ao2_unlock(rtp); -}
\ No newline at end of file +} diff --git a/main/say.c b/main/say.c index 44f55e25a..c97dc9fd8 100644 --- a/main/say.c +++ b/main/say.c @@ -4441,9 +4441,9 @@ int ast_say_date_with_format_da(struct ast_channel *chan, time_t t, const char * } if (!res && next_item(&format[offset + 1]) == 'S') { /* minutes only if seconds follow */ if (tm.tm_min == 1) { - res = wait_file(chan, ints, "digits/minute", lang); + res = wait_file(chan, ints, "minute", lang); } else { - res = wait_file(chan, ints, "digits/minutes", lang); + res = wait_file(chan, ints, "minutes", lang); } } break; @@ -4517,7 +4517,7 @@ int ast_say_date_with_format_da(struct ast_channel *chan, time_t t, const char * if (!res) { res = ast_say_number(chan, tm.tm_sec, ints, lang, "f"); if (!res) { - res = wait_file(chan, ints, "digits/seconds", lang); + res = wait_file(chan, ints, "seconds", lang); } } break; @@ -4637,16 +4637,16 @@ int ast_say_date_with_format_de(struct ast_channel *chan, time_t t, const char * case 'M': /* Minute */ if (next_item(&format[offset + 1]) == 'S') { /* zero 'digits/0' only if seconds follow */ - res = ast_say_number(chan, tm.tm_min, ints, lang, "f"); /* female only if we say digits/minutes */ + res = ast_say_number(chan, tm.tm_min, ints, lang, "f"); /* female only if we say minutes */ } else if (tm.tm_min > 0) { res = ast_say_number(chan, tm.tm_min, ints, lang, (char *) NULL); } if (!res && next_item(&format[offset + 1]) == 'S') { /* minutes only if seconds follow */ if (tm.tm_min == 1) { - res = wait_file(chan, ints, "digits/minute", lang); + res = wait_file(chan, ints, "minute", lang); } else { - res = wait_file(chan, ints, "digits/minutes", lang); + res = wait_file(chan, ints, "minutes", lang); } } break; @@ -4720,7 +4720,7 @@ int ast_say_date_with_format_de(struct ast_channel *chan, time_t t, const char * if (!res) { res = ast_say_number(chan, tm.tm_sec, ints, lang, "f"); if (!res) { - res = wait_file(chan, ints, tm.tm_sec == 1 ? "digits/second" : "digits/seconds", lang); + res = wait_file(chan, ints, tm.tm_sec == 1 ? "second" : "seconds", lang); } } break; @@ -4853,9 +4853,9 @@ int ast_say_date_with_format_is(struct ast_channel *chan, time_t t, const char * if (!res && next_item(&format[offset + 1]) == 'S') { /* minutes only if seconds follow */ /* Say minute/minutes depending on whether minutes end in 1 */ if ((tm.tm_min % 10 == 1) && (tm.tm_min != 11)) { - res = wait_file(chan, ints, "digits/minute", lang); + res = wait_file(chan, ints, "minute", lang); } else { - res = wait_file(chan, ints, "digits/minutes", lang); + res = wait_file(chan, ints, "minutes", lang); } } break; @@ -4930,9 +4930,9 @@ int ast_say_date_with_format_is(struct ast_channel *chan, time_t t, const char * res = ast_say_number(chan, tm.tm_sec, ints, lang, "f"); /* Say minute/minutes depending on whether seconds end in 1 */ if (!res && (tm.tm_sec % 10 == 1) && (tm.tm_sec != 11)) { - res = wait_file(chan, ints, "digits/second", lang); + res = wait_file(chan, ints, "second", lang); } else { - res = wait_file(chan, ints, "digits/seconds", lang); + res = wait_file(chan, ints, "seconds", lang); } } break; @@ -5652,7 +5652,7 @@ int ast_say_date_with_format_fr(struct ast_channel *chan, time_t t, const char * /* Seconds */ res = ast_say_number(chan, tm.tm_sec, ints, lang, (char * ) NULL); if (!res) { - res = wait_file(chan, ints, "digits/second", lang); + res = wait_file(chan, ints, "second", lang); } break; case 'T': @@ -6303,9 +6303,9 @@ int ast_say_date_with_format_pl(struct ast_channel *chan, time_t thetime, const one = tm.tm_sec % 10; if (one > 1 && one < 5 && ten != 1) - res = wait_file(chan, ints, "digits/seconds", lang); + res = wait_file(chan, ints, "seconds", lang); else - res = wait_file(chan, ints, "digits/second", lang); + res = wait_file(chan, ints, "second", lang); } } } @@ -6469,9 +6469,9 @@ int ast_say_date_with_format_pt(struct ast_channel *chan, time_t t, const char * res = ast_say_number(chan, tm.tm_min, ints, lang, NULL); if (!res) { if (tm.tm_min > 1) { - res = wait_file(chan, ints, "digits/minutes", lang); + res = wait_file(chan, ints, "minutes", lang); } else { - res = wait_file(chan, ints, "digits/minute", lang); + res = wait_file(chan, ints, "minute", lang); } } } else { @@ -6567,9 +6567,9 @@ int ast_say_date_with_format_pt(struct ast_channel *chan, time_t t, const char * res = ast_say_number(chan, tm.tm_sec, ints, lang, NULL); if (!res) { if (tm.tm_sec > 1) { - res = wait_file(chan, ints, "digits/seconds", lang); + res = wait_file(chan, ints, "seconds", lang); } else { - res = wait_file(chan, ints, "digits/second", lang); + res = wait_file(chan, ints, "second", lang); } } } else { @@ -6783,7 +6783,7 @@ int ast_say_date_with_format_zh(struct ast_channel *chan, time_t t, const char * } } if (!res) { - res = wait_file(chan, ints, "digits/minute", lang); + res = wait_file(chan, ints, "minute", lang); } break; case 'P': @@ -6867,7 +6867,7 @@ int ast_say_date_with_format_zh(struct ast_channel *chan, time_t t, const char * } } if (!res) { - res = wait_file(chan, ints, "digits/second", lang); + res = wait_file(chan, ints, "second", lang); } break; case 'T': @@ -7022,7 +7022,7 @@ int ast_say_time_hu(struct ast_channel *chan, time_t t, const char *ints, const if (tm.tm_min > 0) { res = ast_say_number(chan, tm.tm_min, ints, lang, "f"); if (!res) - res = ast_streamfile(chan, "digits/minute", lang); + res = ast_streamfile(chan, "minute", lang); } return res; } @@ -7117,9 +7117,9 @@ int ast_say_time_pt_BR(struct ast_channel *chan, time_t t, const char *ints, con res = ast_say_number(chan, tm.tm_min, ints, lang, (char *) NULL); if (!res) { if (tm.tm_min > 1) - res = wait_file(chan, ints, "digits/minutes", lang); + res = wait_file(chan, ints, "minutes", lang); else - res = wait_file(chan, ints, "digits/minute", lang); + res = wait_file(chan, ints, "minute", lang); } } return res; @@ -7179,7 +7179,7 @@ int ast_say_time_zh(struct ast_channel *chan, time_t t, const char *ints, const if (!res) res = ast_say_number(chan, tm.tm_min, ints, lang, (char *) NULL); if (!res) - res = ast_streamfile(chan, "digits/minute", lang); + res = ast_streamfile(chan, "minute", lang); if (!res) res = ast_waitstream(chan, ints); return res; @@ -7602,7 +7602,7 @@ int ast_say_datetime_zh(struct ast_channel *chan, time_t t, const char *ints, co if (!res) res = ast_say_number(chan, tm.tm_min, ints, lang, (char *) NULL); if (!res) - res = ast_streamfile(chan, "digits/minute", lang); + res = ast_streamfile(chan, "minute", lang); if (!res) res = ast_waitstream(chan, ints); return res; @@ -8480,7 +8480,7 @@ static int ast_say_date_with_format_gr(struct ast_channel *chan, time_t t, const if (!res) res = ast_say_number_full_gr(chan, tm.tm_sec, ints, lang, -1, -1); if (!res) - ast_copy_string(nextmsg, "digits/seconds", sizeof(nextmsg)); + ast_copy_string(nextmsg, "seconds", sizeof(nextmsg)); res = wait_file(chan, ints, nextmsg, lang); break; case 'T': diff --git a/main/sdp.c b/main/sdp.c index bfb83e82f..fd10ba8c3 100644 --- a/main/sdp.c +++ b/main/sdp.c @@ -109,11 +109,9 @@ void ast_sdp_t_free(struct ast_sdp_t_line *t_line) ast_free(t_line); } -void ast_sdp_free(struct ast_sdp *sdp) +static void ast_sdp_dtor(void *vdoomed) { - if (!sdp) { - return; - } + struct ast_sdp *sdp = vdoomed; ast_sdp_o_free(sdp->o_line); ast_sdp_s_free(sdp->s_line); @@ -121,7 +119,6 @@ void ast_sdp_free(struct ast_sdp *sdp) ast_sdp_t_free(sdp->t_line); ast_sdp_a_lines_free(sdp->a_lines); ast_sdp_m_lines_free(sdp->m_lines); - ast_free(sdp); } #define COPY_STR_AND_ADVANCE(p, dest, source) \ @@ -314,28 +311,28 @@ struct ast_sdp *ast_sdp_alloc(struct ast_sdp_o_line *o_line, { struct ast_sdp *new_sdp; - new_sdp = ast_calloc(1, sizeof *new_sdp); + new_sdp = ao2_alloc_options(sizeof(*new_sdp), ast_sdp_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK); if (!new_sdp) { return NULL; } new_sdp->a_lines = ast_calloc(1, sizeof(*new_sdp->a_lines)); if (!new_sdp->a_lines) { - ast_sdp_free(new_sdp); + ao2_ref(new_sdp, -1); return NULL; } if (AST_VECTOR_INIT(new_sdp->a_lines, 20)) { - ast_sdp_free(new_sdp); + ao2_ref(new_sdp, -1); return NULL; } new_sdp->m_lines = ast_calloc(1, sizeof(*new_sdp->m_lines)); if (!new_sdp->m_lines) { - ast_sdp_free(new_sdp); + ao2_ref(new_sdp, -1); return NULL; } if (AST_VECTOR_INIT(new_sdp->m_lines, 20)) { - ast_sdp_free(new_sdp); + ao2_ref(new_sdp, -1); return NULL; } @@ -741,6 +738,62 @@ static void process_fmtp_lines(const struct ast_sdp_m_line *m_line, int payload, } } +/*! + * \internal + * \brief Determine the RTP stream direction in the given a lines. + * \since 15.0.0 + * + * \param a_lines Attribute lines to search. + * + * \retval Stream direction on success. + * \retval AST_STREAM_STATE_REMOVED on failure. + * + * \return Nothing + */ +static enum ast_stream_state get_a_line_direction(const struct ast_sdp_a_lines *a_lines) +{ + if (a_lines) { + enum ast_stream_state direction; + int idx; + const struct ast_sdp_a_line *a_line; + + for (idx = 0; idx < AST_VECTOR_SIZE(a_lines); ++idx) { + a_line = AST_VECTOR_GET(a_lines, idx); + direction = ast_stream_str2state(a_line->name); + if (direction != AST_STREAM_STATE_REMOVED) { + return direction; + } + } + } + + return AST_STREAM_STATE_REMOVED; +} + +/*! + * \internal + * \brief Determine the RTP stream direction. + * \since 15.0.0 + * + * \param a_lines The SDP media global attributes + * \param m_line The SDP media section to convert + * + * \return Stream direction + */ +static enum ast_stream_state get_stream_direction(const struct ast_sdp_a_lines *a_lines, const struct ast_sdp_m_line *m_line) +{ + enum ast_stream_state direction; + + direction = get_a_line_direction(m_line->a_lines); + if (direction != AST_STREAM_STATE_REMOVED) { + return direction; + } + direction = get_a_line_direction(a_lines); + if (direction != AST_STREAM_STATE_REMOVED) { + return direction; + } + return AST_STREAM_STATE_SENDRECV; +} + /* * Needed so we don't have an external function referenced as data. * The dynamic linker doesn't handle that very well. @@ -758,13 +811,14 @@ static void rtp_codecs_free(struct ast_rtp_codecs *codecs) * Given an m-line from an SDP, convert it into an ast_stream structure. * This takes formats, as well as clock-rate and fmtp attributes into account. * + * \param a_lines The SDP media global attributes * \param m_line The SDP media section to convert * \param g726_non_standard Non-zero if G.726 is non-standard * * \retval NULL An error occurred * \retval non-NULL The converted stream */ -static struct ast_stream *get_stream_from_m(const struct ast_sdp_m_line *m_line, int g726_non_standard) +static struct ast_stream *get_stream_from_m(const struct ast_sdp_a_lines *a_lines, const struct ast_sdp_m_line *m_line, int g726_non_standard) { int i; int non_ast_fmts; @@ -793,6 +847,14 @@ static struct ast_stream *get_stream_from_m(const struct ast_sdp_m_line *m_line, ao2_ref(caps, -1); return NULL; } + ast_stream_set_data(stream, AST_STREAM_DATA_RTP_CODECS, codecs, + (ast_stream_data_free_fn) rtp_codecs_free); + + if (!m_line->port) { + /* Stream is declined. There may not be any attributes. */ + ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED); + break; + } options = g726_non_standard ? AST_RTP_OPT_G726_NONSTANDARD : 0; for (i = 0; i < ast_sdp_m_get_payload_count(m_line); ++i) { @@ -819,10 +881,16 @@ static struct ast_stream *get_stream_from_m(const struct ast_sdp_m_line *m_line, } ast_rtp_codecs_payload_formats(codecs, caps, &non_ast_fmts); - ast_stream_set_data(stream, AST_STREAM_DATA_RTP_CODECS, codecs, - (ast_stream_data_free_fn) rtp_codecs_free); + + ast_stream_set_state(stream, get_stream_direction(a_lines, m_line)); break; case AST_MEDIA_TYPE_IMAGE: + if (!m_line->port) { + /* Stream is declined. There may not be any attributes. */ + ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED); + break; + } + for (i = 0; i < ast_sdp_m_get_payload_count(m_line); ++i) { struct ast_sdp_payload *payload; @@ -830,12 +898,15 @@ static struct ast_stream *get_stream_from_m(const struct ast_sdp_m_line *m_line, payload = ast_sdp_m_get_payload(m_line, i); if (!strcasecmp(payload->fmt, "t38")) { ast_format_cap_append(caps, ast_format_t38, 0); + ast_stream_set_state(stream, AST_STREAM_STATE_SENDRECV); } } break; case AST_MEDIA_TYPE_UNKNOWN: case AST_MEDIA_TYPE_TEXT: case AST_MEDIA_TYPE_END: + /* Consider these unsupported streams as declined */ + ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED); break; } @@ -858,7 +929,7 @@ struct ast_stream_topology *ast_get_topology_from_sdp(const struct ast_sdp *sdp, for (i = 0; i < ast_sdp_get_m_count(sdp); ++i) { struct ast_stream *stream; - stream = get_stream_from_m(ast_sdp_get_m(sdp, i), g726_non_standard); + stream = get_stream_from_m(sdp->a_lines, ast_sdp_get_m(sdp, i), g726_non_standard); if (!stream) { /* * The topology cannot match the SDP because diff --git a/main/sdp_options.c b/main/sdp_options.c index a938583c6..79efbafa0 100644 --- a/main/sdp_options.c +++ b/main/sdp_options.c @@ -27,6 +27,7 @@ #define DEFAULT_ICE AST_SDP_ICE_DISABLED #define DEFAULT_IMPL AST_SDP_IMPL_STRING #define DEFAULT_ENCRYPTION AST_SDP_ENCRYPTION_DISABLED +#define DEFAULT_MAX_STREAMS 16 /* Set to match our PJPROJECT PJMEDIA_MAX_SDP_MEDIA. */ #define DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(field, assert_on_null) \ void ast_sdp_options_set_##field(struct ast_sdp_options *options, const char *value) \ @@ -60,6 +61,12 @@ DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(sdpowner, 0); DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(sdpsession, 0); DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(rtp_engine, 0); +DEFINE_GETTERS_SETTERS_FOR(void *, state_context); +DEFINE_GETTERS_SETTERS_FOR(ast_sdp_answerer_modify_cb, answerer_modify_cb); +DEFINE_GETTERS_SETTERS_FOR(ast_sdp_offerer_modify_cb, offerer_modify_cb); +DEFINE_GETTERS_SETTERS_FOR(ast_sdp_offerer_config_cb, offerer_config_cb); +DEFINE_GETTERS_SETTERS_FOR(ast_sdp_preapply_cb, preapply_cb); +DEFINE_GETTERS_SETTERS_FOR(ast_sdp_postapply_cb, postapply_cb); DEFINE_GETTERS_SETTERS_FOR(unsigned int, rtp_symmetric); DEFINE_GETTERS_SETTERS_FOR(unsigned int, udptl_symmetric); DEFINE_GETTERS_SETTERS_FOR(enum ast_t38_ec_modes, udptl_error_correction); @@ -71,6 +78,7 @@ DEFINE_GETTERS_SETTERS_FOR(unsigned int, tos_audio); DEFINE_GETTERS_SETTERS_FOR(unsigned int, cos_audio); DEFINE_GETTERS_SETTERS_FOR(unsigned int, tos_video); DEFINE_GETTERS_SETTERS_FOR(unsigned int, cos_video); +DEFINE_GETTERS_SETTERS_FOR(unsigned int, max_streams); DEFINE_GETTERS_SETTERS_FOR(enum ast_sdp_options_dtmf, dtmf); DEFINE_GETTERS_SETTERS_FOR(enum ast_sdp_options_ice, ice); DEFINE_GETTERS_SETTERS_FOR(enum ast_sdp_options_impl, impl); @@ -110,12 +118,87 @@ void ast_sdp_options_set_sched_type(struct ast_sdp_options *options, enum ast_me } } +struct ast_format_cap *ast_sdp_options_get_format_cap_type(const struct ast_sdp_options *options, + enum ast_media_type type) +{ + struct ast_format_cap *cap = NULL; + + switch (type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + case AST_MEDIA_TYPE_IMAGE: + case AST_MEDIA_TYPE_TEXT: + cap = options->caps[type]; + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_END: + break; + } + return cap; +} + +void ast_sdp_options_set_format_cap_type(struct ast_sdp_options *options, + enum ast_media_type type, struct ast_format_cap *cap) +{ + switch (type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + case AST_MEDIA_TYPE_IMAGE: + case AST_MEDIA_TYPE_TEXT: + ao2_cleanup(options->caps[type]); + options->caps[type] = NULL; + if (cap && !ast_format_cap_empty(cap)) { + ao2_ref(cap, +1); + options->caps[type] = cap; + } + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_END: + break; + } +} + +void ast_sdp_options_set_format_caps(struct ast_sdp_options *options, + struct ast_format_cap *cap) +{ + enum ast_media_type type; + + for (type = AST_MEDIA_TYPE_UNKNOWN; type < AST_MEDIA_TYPE_END; ++type) { + ao2_cleanup(options->caps[type]); + options->caps[type] = NULL; + } + + if (!cap || ast_format_cap_empty(cap)) { + return; + } + + for (type = AST_MEDIA_TYPE_UNKNOWN + 1; type < AST_MEDIA_TYPE_END; ++type) { + struct ast_format_cap *type_cap; + + type_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!type_cap) { + continue; + } + + ast_format_cap_set_framing(type_cap, ast_format_cap_get_framing(cap)); + if (ast_format_cap_append_from_cap(type_cap, cap, type) + || ast_format_cap_empty(type_cap)) { + ao2_ref(type_cap, -1); + continue; + } + + /* This takes the allocation reference */ + options->caps[type] = type_cap; + } +} + static void set_defaults(struct ast_sdp_options *options) { options->dtmf = DEFAULT_DTMF; options->ice = DEFAULT_ICE; options->impl = DEFAULT_IMPL; options->encryption = DEFAULT_ENCRYPTION; + options->max_streams = DEFAULT_MAX_STREAMS; } struct ast_sdp_options *ast_sdp_options_alloc(void) @@ -138,6 +221,15 @@ struct ast_sdp_options *ast_sdp_options_alloc(void) void ast_sdp_options_free(struct ast_sdp_options *options) { + enum ast_media_type type; + + if (!options) { + return; + } + + for (type = AST_MEDIA_TYPE_UNKNOWN; type < AST_MEDIA_TYPE_END; ++type) { + ao2_cleanup(options->caps[type]); + } ast_string_field_free_memory(options); ast_free(options); } diff --git a/main/sdp_private.h b/main/sdp_private.h index 62228a5c8..48bedc802 100644 --- a/main/sdp_private.h +++ b/main/sdp_private.h @@ -24,7 +24,7 @@ struct ast_sdp_options { AST_DECLARE_STRING_FIELDS( - /*! Media address to use in SDP */ + /*! Media address to advertise in SDP session c= line */ AST_STRING_FIELD(media_address); /*! Optional address of the interface media should use. */ AST_STRING_FIELD(interface_address); @@ -37,12 +37,25 @@ struct ast_sdp_options { ); /*! Scheduler context for the media stream types (Mainly for RTP) */ struct ast_sched_context *sched[AST_MEDIA_TYPE_END]; + /*! Capabilities to create new streams of the indexed media type. */ + struct ast_format_cap *caps[AST_MEDIA_TYPE_END]; + /*! User supplied context data pointer for the SDP state. */ + void *state_context; + /*! Modify negotiated topology before create answer SDP callback. */ + ast_sdp_answerer_modify_cb answerer_modify_cb; + /*! Modify proposed topology before create offer SDP callback. */ + ast_sdp_offerer_modify_cb offerer_modify_cb; + /*! Configure proposed topology extra stream options before create offer SDP callback. */ + ast_sdp_offerer_config_cb offerer_config_cb; + /*! Negotiated topology is about to be applied callback. */ + ast_sdp_preapply_cb preapply_cb; + /*! Negotiated topology was just applied callback. */ + ast_sdp_postapply_cb postapply_cb; struct { unsigned int rtp_symmetric:1; unsigned int udptl_symmetric:1; unsigned int rtp_ipv6:1; unsigned int g726_non_standard:1; - unsigned int locally_held:1; unsigned int rtcp_mux:1; unsigned int ssrc:1; }; @@ -52,6 +65,8 @@ struct ast_sdp_options { unsigned int tos_video; unsigned int cos_video; unsigned int udptl_far_max_datagram; + /*! Maximum number of streams to allow. */ + unsigned int max_streams; }; enum ast_sdp_options_dtmf dtmf; enum ast_sdp_options_ice ice; diff --git a/main/sdp_state.c b/main/sdp_state.c index 99421ad4d..a77d96da5 100644 --- a/main/sdp_state.c +++ b/main/sdp_state.c @@ -85,12 +85,39 @@ struct sdp_state_stream { }; /*! An explicit connection address for this stream */ struct ast_sockaddr connection_address; - /*! Whether this stream is held or not */ - unsigned int locally_held; + /*! + * \brief Stream is on hold by remote side + * + * \note This flag is never set on the + * sdp_state->proposed_capabilities->streams states. This is useful + * when the remote sends us a reINVITE with a deferred SDP to place + * us on and off of hold. + */ + unsigned int remotely_held:1; + /*! Stream is on hold by local side */ + unsigned int locally_held:1; /*! UDPTL session parameters */ struct ast_control_t38_parameters t38_local_params; }; +static int sdp_is_stream_type_supported(enum ast_media_type type) +{ + int is_supported = 0; + + switch (type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + case AST_MEDIA_TYPE_IMAGE: + is_supported = 1; + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; + } + return is_supported; +} + static void sdp_state_rtp_destroy(void *obj) { struct sdp_state_rtp *rtp = obj; @@ -135,8 +162,6 @@ struct sdp_state_capabilities { struct ast_stream_topology *topology; /*! Additional information about the streams */ struct sdp_state_streams streams; - /*! An explicit global connection address */ - struct ast_sockaddr connection_address; }; static void sdp_state_capabilities_free(struct sdp_state_capabilities *capabilities) @@ -269,30 +294,80 @@ static struct sdp_state_udptl *create_udptl(const struct ast_sdp_options *option return udptl; } +static struct ast_stream *merge_local_stream(const struct ast_sdp_options *options, + const struct ast_stream *update); + static struct sdp_state_capabilities *sdp_initialize_state_capabilities(const struct ast_stream_topology *topology, const struct ast_sdp_options *options) { struct sdp_state_capabilities *capabilities; - int i; + struct ast_stream *stream; + unsigned int topology_count; + unsigned int max_streams; + unsigned int idx; capabilities = ast_calloc(1, sizeof(*capabilities)); if (!capabilities) { return NULL; } - capabilities->topology = ast_stream_topology_clone(topology); + capabilities->topology = ast_stream_topology_alloc(); if (!capabilities->topology) { sdp_state_capabilities_free(capabilities); return NULL; } - if (AST_VECTOR_INIT(&capabilities->streams, ast_stream_topology_get_count(topology))) { + max_streams = ast_sdp_options_get_max_streams(options); + if (topology) { + topology_count = ast_stream_topology_get_count(topology); + } else { + topology_count = 0; + } + + /* Gather acceptable streams from the initial topology */ + for (idx = 0; idx < topology_count; ++idx) { + stream = ast_stream_topology_get_stream(topology, idx); + if (!sdp_is_stream_type_supported(ast_stream_get_type(stream))) { + /* Delete the unsupported stream from the initial topology */ + continue; + } + if (max_streams <= ast_stream_topology_get_count(capabilities->topology)) { + /* Cannot support any more streams */ + break; + } + + stream = merge_local_stream(options, stream); + if (!stream) { + sdp_state_capabilities_free(capabilities); + return NULL; + } + + if (ast_stream_topology_append_stream(capabilities->topology, stream) < 0) { + ast_stream_free(stream); + sdp_state_capabilities_free(capabilities); + return NULL; + } + } + + /* + * Remove trailing declined streams from the initial built topology. + * No need to waste space in the SDP with these unused slots. + */ + for (idx = ast_stream_topology_get_count(capabilities->topology); idx--;) { + stream = ast_stream_topology_get_stream(capabilities->topology, idx); + if (ast_stream_get_state(stream) != AST_STREAM_STATE_REMOVED) { + break; + } + ast_stream_topology_del_stream(capabilities->topology, idx); + } + + topology_count = ast_stream_topology_get_count(capabilities->topology); + if (AST_VECTOR_INIT(&capabilities->streams, topology_count)) { sdp_state_capabilities_free(capabilities); return NULL; } - ast_sockaddr_setnull(&capabilities->connection_address); - for (i = 0; i < ast_stream_topology_get_count(topology); ++i) { + for (idx = 0; idx < topology_count; ++idx) { struct sdp_state_stream *state_stream; state_stream = ast_calloc(1, sizeof(*state_stream)); @@ -301,32 +376,34 @@ static struct sdp_state_capabilities *sdp_initialize_state_capabilities(const st return NULL; } - state_stream->type = ast_stream_get_type(ast_stream_topology_get_stream(topology, i)); - switch (state_stream->type) { - case AST_MEDIA_TYPE_AUDIO: - case AST_MEDIA_TYPE_VIDEO: - state_stream->rtp = create_rtp(options, state_stream->type); - if (!state_stream->rtp) { - sdp_state_stream_free(state_stream); - sdp_state_capabilities_free(capabilities); - return NULL; - } - break; - case AST_MEDIA_TYPE_IMAGE: - state_stream->udptl = create_udptl(options); - if (!state_stream->udptl) { - sdp_state_stream_free(state_stream); - sdp_state_capabilities_free(capabilities); - return NULL; + stream = ast_stream_topology_get_stream(capabilities->topology, idx); + state_stream->type = ast_stream_get_type(stream); + if (ast_stream_get_state(stream) != AST_STREAM_STATE_REMOVED) { + switch (state_stream->type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + state_stream->rtp = create_rtp(options, state_stream->type); + if (!state_stream->rtp) { + sdp_state_stream_free(state_stream); + sdp_state_capabilities_free(capabilities); + return NULL; + } + break; + case AST_MEDIA_TYPE_IMAGE: + state_stream->udptl = create_udptl(options); + if (!state_stream->udptl) { + sdp_state_stream_free(state_stream); + sdp_state_capabilities_free(capabilities); + return NULL; + } + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + /* Unsupported stream type already handled earlier */ + ast_assert(0); + break; } - break; - case AST_MEDIA_TYPE_UNKNOWN: - case AST_MEDIA_TYPE_TEXT: - case AST_MEDIA_TYPE_END: - ast_assert(0); - sdp_state_stream_free(state_stream); - sdp_state_capabilities_free(capabilities); - return NULL; } if (AST_VECTOR_APPEND(&capabilities->streams, state_stream)) { @@ -350,12 +427,12 @@ static struct sdp_state_capabilities *sdp_initialize_state_capabilities(const st * is allocated, this topology is used to create the proposed_capabilities. * * If we are the SDP offerer, then the proposed_capabilities are what are used - * to generate the SDP offer. When the SDP answer arrives, the proposed capabilities - * are merged with the SDP answer to create the negotiated capabilities. + * to generate the offer SDP. When the answer SDP arrives, the proposed capabilities + * are merged with the answer SDP to create the negotiated capabilities. * - * If we are the SDP answerer, then the incoming SDP offer is merged with our + * If we are the SDP answerer, then the incoming offer SDP is merged with our * proposed capabilities to to create the negotiated capabilities. These negotiated - * capabilities are what we send in our SDP answer. + * capabilities are what we send in our answer SDP. * * Any changes that a user of the API performs will occur on the proposed capabilities. * The negotiated capabilities are only altered based on actual SDP negotiation. This is @@ -367,17 +444,33 @@ struct ast_sdp_state { struct sdp_state_capabilities *negotiated_capabilities; /*! Proposed capabilities */ struct sdp_state_capabilities *proposed_capabilities; - /*! Local SDP. Generated via the options and currently negotiated/proposed capabilities. */ + /*! + * \brief New topology waiting to be merged. + * + * \details + * Repeated topology updates are merged into each other here until + * negotiations are restarted and we create an offer. + */ + struct ast_stream_topology *pending_topology_update; + /*! Local SDP. Generated via the options and negotiated/proposed capabilities. */ struct ast_sdp *local_sdp; + /*! Saved remote SDP */ + struct ast_sdp *remote_sdp; /*! SDP options. Configured options beyond media capabilities. */ struct ast_sdp_options *options; /*! Translator that puts SDPs into the expected representation */ struct ast_sdp_translator *translator; + /*! An explicit global connection address */ + struct ast_sockaddr connection_address; /*! The role that we occupy in SDP negotiation */ enum ast_sdp_role role; + /*! TRUE if all streams on hold by local side */ + unsigned int locally_held:1; + /*! TRUE if the remote offer resulted in all streams being declined. */ + unsigned int remote_offer_rejected:1; }; -struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *streams, +struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *topology, struct ast_sdp_options *options) { struct ast_sdp_state *sdp_state; @@ -395,7 +488,7 @@ struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *streams, return NULL; } - sdp_state->proposed_capabilities = sdp_initialize_state_capabilities(streams, options); + sdp_state->proposed_capabilities = sdp_initialize_state_capabilities(topology, options); if (!sdp_state->proposed_capabilities) { ast_sdp_state_free(sdp_state); return NULL; @@ -414,12 +507,217 @@ void ast_sdp_state_free(struct ast_sdp_state *sdp_state) sdp_state_capabilities_free(sdp_state->negotiated_capabilities); sdp_state_capabilities_free(sdp_state->proposed_capabilities); - ast_sdp_free(sdp_state->local_sdp); + ao2_cleanup(sdp_state->local_sdp); + ao2_cleanup(sdp_state->remote_sdp); ast_sdp_options_free(sdp_state->options); ast_sdp_translator_free(sdp_state->translator); ast_free(sdp_state); } +/*! + * \internal + * \brief Allow a configured callback to alter the new negotiated joint topology. + * \since 15.0.0 + * + * \details + * The callback can alter topology stream names, formats, or decline streams. + * + * \param sdp_state + * \param topology Joint topology that we intend to generate the answer SDP. + * + * \return Nothing + */ +static void sdp_state_cb_answerer_modify_topology(const struct ast_sdp_state *sdp_state, + struct ast_stream_topology *topology) +{ + ast_sdp_answerer_modify_cb cb; + + cb = ast_sdp_options_get_answerer_modify_cb(sdp_state->options); + if (cb) { + void *context; + const struct ast_stream_topology *neg_topology;/*!< Last negotiated topology */ +#ifdef AST_DEVMODE + struct ast_stream *stream; + int idx; + enum ast_media_type type[ast_stream_topology_get_count(topology)]; + enum ast_stream_state state[ast_stream_topology_get_count(topology)]; + + /* + * Save stream types and states to validate that they don't + * get changed unexpectedly. + */ + for (idx = 0; idx < ast_stream_topology_get_count(topology); ++idx) { + stream = ast_stream_topology_get_stream(topology, idx); + type[idx] = ast_stream_get_type(stream); + state[idx] = ast_stream_get_state(stream); + } +#endif + + context = ast_sdp_options_get_state_context(sdp_state->options); + neg_topology = sdp_state->negotiated_capabilities + ? sdp_state->negotiated_capabilities->topology : NULL; + cb(context, neg_topology, topology); + +#ifdef AST_DEVMODE + for (idx = 0; idx < ast_stream_topology_get_count(topology); ++idx) { + stream = ast_stream_topology_get_stream(topology, idx); + + /* Check that active streams have at least one format */ + ast_assert(ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED + || (ast_stream_get_formats(stream) + && ast_format_cap_count(ast_stream_get_formats(stream)))); + + /* Check that stream types didn't change. */ + ast_assert(type[idx] == ast_stream_get_type(stream)); + + /* Check that streams didn't get resurected. */ + ast_assert(state[idx] != AST_STREAM_STATE_REMOVED + || ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED); + } +#endif + } +} + +/*! + * \internal + * \brief Allow a configured callback to alter the merged local topology. + * \since 15.0.0 + * + * \details + * The callback can modify streams in the merged topology. The + * callback can decline, add/remove/update formats, or rename + * streams. Changing anything else on the streams is likely to not + * end well. + * + * \param sdp_state + * \param topology Merged topology that we intend to generate the offer SDP. + * + * \return Nothing + */ +static void sdp_state_cb_offerer_modify_topology(const struct ast_sdp_state *sdp_state, + struct ast_stream_topology *topology) +{ + ast_sdp_offerer_modify_cb cb; + + cb = ast_sdp_options_get_offerer_modify_cb(sdp_state->options); + if (cb) { + void *context; + const struct ast_stream_topology *neg_topology;/*!< Last negotiated topology */ + + context = ast_sdp_options_get_state_context(sdp_state->options); + neg_topology = sdp_state->negotiated_capabilities + ? sdp_state->negotiated_capabilities->topology : NULL; + cb(context, neg_topology, topology); + +#ifdef AST_DEVMODE + { + struct ast_stream *stream; + int idx; + + /* Check that active streams have at least one format */ + for (idx = 0; idx < ast_stream_topology_get_count(topology); ++idx) { + stream = ast_stream_topology_get_stream(topology, idx); + ast_assert(ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED + || (ast_stream_get_formats(stream) + && ast_format_cap_count(ast_stream_get_formats(stream)))); + } + } +#endif + } +} + +/*! + * \internal + * \brief Allow a configured callback to configure the merged local topology. + * \since 15.0.0 + * + * \details + * The callback can configure other parameters associated with each + * active stream on the topology. The callback can call several SDP + * API calls to configure the proposed capabilities of the streams + * before we create the offer SDP. For example, the callback could + * configure a stream specific connection address, T.38 parameters, + * RTP instance, or UDPTL instance parameters. + * + * \param sdp_state + * \param topology Merged topology that we intend to generate the offer SDP. + * + * \return Nothing + */ +static void sdp_state_cb_offerer_config_topology(const struct ast_sdp_state *sdp_state, + const struct ast_stream_topology *topology) +{ + ast_sdp_offerer_config_cb cb; + + cb = ast_sdp_options_get_offerer_config_cb(sdp_state->options); + if (cb) { + void *context; + + context = ast_sdp_options_get_state_context(sdp_state->options); + cb(context, topology); + } +} + +/*! + * \internal + * \brief Call any registered pre-apply topology callback. + * \since 15.0.0 + * + * \param sdp_state + * \param topology + * + * \return Nothing + */ +static void sdp_state_cb_preapply_topology(const struct ast_sdp_state *sdp_state, + const struct ast_stream_topology *topology) +{ + ast_sdp_preapply_cb cb; + + cb = ast_sdp_options_get_preapply_cb(sdp_state->options); + if (cb) { + void *context; + + context = ast_sdp_options_get_state_context(sdp_state->options); + cb(context, topology); + } +} + +/*! + * \internal + * \brief Call any registered post-apply topology callback. + * \since 15.0.0 + * + * \param sdp_state + * \param topology + * + * \return Nothing + */ +static void sdp_state_cb_postapply_topology(const struct ast_sdp_state *sdp_state, + const struct ast_stream_topology *topology) +{ + ast_sdp_postapply_cb cb; + + cb = ast_sdp_options_get_postapply_cb(sdp_state->options); + if (cb) { + void *context; + + context = ast_sdp_options_get_state_context(sdp_state->options); + cb(context, topology); + } +} + +static const struct sdp_state_capabilities *sdp_state_get_joint_capabilities( + const struct ast_sdp_state *sdp_state) +{ + ast_assert(sdp_state != NULL); + + if (sdp_state->negotiated_capabilities) { + return sdp_state->negotiated_capabilities; + } + + return sdp_state->proposed_capabilities; +} + static struct sdp_state_stream *sdp_state_get_stream(const struct ast_sdp_state *sdp_state, int stream_index) { if (stream_index >= AST_VECTOR_SIZE(&sdp_state->proposed_capabilities->streams)) { @@ -429,6 +727,18 @@ static struct sdp_state_stream *sdp_state_get_stream(const struct ast_sdp_state return AST_VECTOR_GET(&sdp_state->proposed_capabilities->streams, stream_index); } +static struct sdp_state_stream *sdp_state_get_joint_stream(const struct ast_sdp_state *sdp_state, int stream_index) +{ + const struct sdp_state_capabilities *capabilities; + + capabilities = sdp_state_get_joint_capabilities(sdp_state); + if (AST_VECTOR_SIZE(&capabilities->streams) <= stream_index) { + return NULL; + } + + return AST_VECTOR_GET(&capabilities->streams, stream_index); +} + struct ast_rtp_instance *ast_sdp_state_get_rtp_instance( const struct ast_sdp_state *sdp_state, int stream_index) { @@ -468,35 +778,34 @@ const struct ast_sockaddr *ast_sdp_state_get_connection_address(const struct ast { ast_assert(sdp_state != NULL); - return &sdp_state->proposed_capabilities->connection_address; + return &sdp_state->connection_address; } -int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_state, - int stream_index, struct ast_sockaddr *address) +static int sdp_state_stream_get_connection_address(const struct ast_sdp_state *sdp_state, + struct sdp_state_stream *stream_state, struct ast_sockaddr *address) { - struct sdp_state_stream *stream_state; - ast_assert(sdp_state != NULL); + ast_assert(stream_state != NULL); ast_assert(address != NULL); - stream_state = sdp_state_get_stream(sdp_state, stream_index); - if (!stream_state) { - return -1; - } - /* If an explicit connection address has been provided for the stream return it */ if (!ast_sockaddr_isnull(&stream_state->connection_address)) { ast_sockaddr_copy(address, &stream_state->connection_address); return 0; } - switch (ast_stream_get_type(ast_stream_topology_get_stream(sdp_state->proposed_capabilities->topology, - stream_index))) { + switch (stream_state->type) { case AST_MEDIA_TYPE_AUDIO: case AST_MEDIA_TYPE_VIDEO: + if (!stream_state->rtp->instance) { + return -1; + } ast_rtp_instance_get_local_address(stream_state->rtp->instance, address); break; case AST_MEDIA_TYPE_IMAGE: + if (!stream_state->udptl->instance) { + return -1; + } ast_udptl_get_us(stream_state->udptl->instance, address); break; case AST_MEDIA_TYPE_UNKNOWN: @@ -505,27 +814,45 @@ int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_ return -1; } + if (ast_sockaddr_isnull(address)) { + /* No address is set on the stream state. */ + return -1; + } + /* If an explicit global connection address is set use it here for the IP part */ - if (!ast_sockaddr_isnull(&sdp_state->proposed_capabilities->connection_address)) { + if (!ast_sockaddr_isnull(&sdp_state->connection_address)) { int port = ast_sockaddr_port(address); - ast_sockaddr_copy(address, &sdp_state->proposed_capabilities->connection_address); + ast_sockaddr_copy(address, &sdp_state->connection_address); ast_sockaddr_set_port(address, port); } return 0; } -const struct ast_stream_topology *ast_sdp_state_get_joint_topology( - const struct ast_sdp_state *sdp_state) +int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_state, + int stream_index, struct ast_sockaddr *address) { + struct sdp_state_stream *stream_state; + ast_assert(sdp_state != NULL); + ast_assert(address != NULL); - if (sdp_state->negotiated_capabilities) { - return sdp_state->negotiated_capabilities->topology; + stream_state = sdp_state_get_stream(sdp_state, stream_index); + if (!stream_state) { + return -1; } - return sdp_state->proposed_capabilities->topology; + return sdp_state_stream_get_connection_address(sdp_state, stream_state, address); +} + +const struct ast_stream_topology *ast_sdp_state_get_joint_topology( + const struct ast_sdp_state *sdp_state) +{ + const struct sdp_state_capabilities *capabilities; + + capabilities = sdp_state_get_joint_capabilities(sdp_state); + return capabilities->topology; } const struct ast_stream_topology *ast_sdp_state_get_local_topology( @@ -544,50 +871,185 @@ const struct ast_sdp_options *ast_sdp_state_get_options( return sdp_state->options; } +static struct ast_stream *decline_stream(enum ast_media_type type, const char *name) +{ + struct ast_stream *stream; + + if (!name) { + name = ast_codec_media_type2str(type); + } + stream = ast_stream_alloc(name, type); + if (!stream) { + return NULL; + } + ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED); + return stream; +} + +/*! + * \brief Merge an update stream into a local stream. + * + * \param options SDP Options + * \param update An updated stream + * + * \retval NULL An error occurred + * \retval non-NULL The joint stream created + */ +static struct ast_stream *merge_local_stream(const struct ast_sdp_options *options, + const struct ast_stream *update) +{ + struct ast_stream *joint_stream; + struct ast_format_cap *joint_cap; + struct ast_format_cap *allowed_cap; + struct ast_format_cap *update_cap; + enum ast_stream_state joint_state; + + joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!joint_cap) { + return NULL; + } + + update_cap = ast_stream_get_formats(update); + allowed_cap = ast_sdp_options_get_format_cap_type(options, + ast_stream_get_type(update)); + if (allowed_cap && update_cap) { + struct ast_str *allowed_buf = ast_str_alloca(128); + struct ast_str *update_buf = ast_str_alloca(128); + struct ast_str *joint_buf = ast_str_alloca(128); + + ast_format_cap_get_compatible(allowed_cap, update_cap, joint_cap); + ast_debug(3, + "Filtered update '%s' with allowed '%s' to get joint '%s'. Joint has %zu formats\n", + ast_format_cap_get_names(update_cap, &update_buf), + ast_format_cap_get_names(allowed_cap, &allowed_buf), + ast_format_cap_get_names(joint_cap, &joint_buf), + ast_format_cap_count(joint_cap)); + } + + /* Determine the joint stream state */ + joint_state = AST_STREAM_STATE_REMOVED; + if (ast_stream_get_state(update) != AST_STREAM_STATE_REMOVED + && ast_format_cap_count(joint_cap)) { + joint_state = AST_STREAM_STATE_SENDRECV; + } + + joint_stream = ast_stream_alloc(ast_stream_get_name(update), + ast_stream_get_type(update)); + if (joint_stream) { + ast_stream_set_state(joint_stream, joint_state); + if (joint_state != AST_STREAM_STATE_REMOVED) { + ast_stream_set_formats(joint_stream, joint_cap); + } + } + + ao2_ref(joint_cap, -1); + + return joint_stream; +} + /*! - * \brief Merge two streams into a joint stream. + * \brief Merge a remote stream into a local stream. * - * \param local Our local stream + * \param sdp_state + * \param local Our local stream (NULL if creating new stream) + * \param locally_held Nonzero if the local stream is held * \param remote A remote stream * * \retval NULL An error occurred * \retval non-NULL The joint stream created */ -static struct ast_stream *merge_streams(const struct ast_stream *local, +static struct ast_stream *merge_remote_stream(const struct ast_sdp_state *sdp_state, + const struct ast_stream *local, unsigned int locally_held, const struct ast_stream *remote) { struct ast_stream *joint_stream; struct ast_format_cap *joint_cap; struct ast_format_cap *local_cap; struct ast_format_cap *remote_cap; - struct ast_str *local_buf = ast_str_alloca(128); - struct ast_str *remote_buf = ast_str_alloca(128); - struct ast_str *joint_buf = ast_str_alloca(128); - - joint_stream = ast_stream_alloc(ast_codec_media_type2str(ast_stream_get_type(remote)), - ast_stream_get_type(remote)); - if (!joint_stream) { - return NULL; - } + const char *joint_name; + enum ast_stream_state joint_state; + enum ast_stream_state remote_state; joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!joint_cap) { - ast_stream_free(joint_stream); return NULL; } - local_cap = ast_stream_get_formats(local); remote_cap = ast_stream_get_formats(remote); + if (local) { + local_cap = ast_stream_get_formats(local); + } else { + local_cap = ast_sdp_options_get_format_cap_type(sdp_state->options, + ast_stream_get_type(remote)); + } + if (local_cap && remote_cap) { + struct ast_str *local_buf = ast_str_alloca(128); + struct ast_str *remote_buf = ast_str_alloca(128); + struct ast_str *joint_buf = ast_str_alloca(128); + + ast_format_cap_get_compatible(local_cap, remote_cap, joint_cap); + ast_debug(3, + "Combined local '%s' with remote '%s' to get joint '%s'. Joint has %zu formats\n", + ast_format_cap_get_names(local_cap, &local_buf), + ast_format_cap_get_names(remote_cap, &remote_buf), + ast_format_cap_get_names(joint_cap, &joint_buf), + ast_format_cap_count(joint_cap)); + } + + /* Determine the joint stream state */ + remote_state = ast_stream_get_state(remote); + joint_state = AST_STREAM_STATE_REMOVED; + if ((!local || ast_stream_get_state(local) != AST_STREAM_STATE_REMOVED) + && ast_format_cap_count(joint_cap)) { + if (sdp_state->locally_held || locally_held) { + switch (remote_state) { + case AST_STREAM_STATE_REMOVED: + break; + case AST_STREAM_STATE_INACTIVE: + joint_state = AST_STREAM_STATE_INACTIVE; + break; + case AST_STREAM_STATE_SENDRECV: + joint_state = AST_STREAM_STATE_SENDONLY; + break; + case AST_STREAM_STATE_SENDONLY: + joint_state = AST_STREAM_STATE_INACTIVE; + break; + case AST_STREAM_STATE_RECVONLY: + joint_state = AST_STREAM_STATE_SENDONLY; + break; + } + } else { + switch (remote_state) { + case AST_STREAM_STATE_REMOVED: + break; + case AST_STREAM_STATE_INACTIVE: + joint_state = AST_STREAM_STATE_RECVONLY; + break; + case AST_STREAM_STATE_SENDRECV: + joint_state = AST_STREAM_STATE_SENDRECV; + break; + case AST_STREAM_STATE_SENDONLY: + joint_state = AST_STREAM_STATE_RECVONLY; + break; + case AST_STREAM_STATE_RECVONLY: + joint_state = AST_STREAM_STATE_SENDRECV; + break; + } + } + } - ast_format_cap_get_compatible(local_cap, remote_cap, joint_cap); - - ast_debug(3, "Combined local '%s' with remote '%s' to get joint '%s'. Joint has %zu formats\n", - ast_format_cap_get_names(local_cap, &local_buf), - ast_format_cap_get_names(remote_cap, &remote_buf), - ast_format_cap_get_names(joint_cap, &joint_buf), - ast_format_cap_count(joint_cap)); - - ast_stream_set_formats(joint_stream, joint_cap); + if (local) { + joint_name = ast_stream_get_name(local); + } else { + joint_name = ast_codec_media_type2str(ast_stream_get_type(remote)); + } + joint_stream = ast_stream_alloc(joint_name, ast_stream_get_type(remote)); + if (joint_stream) { + ast_stream_set_state(joint_stream, joint_state); + if (joint_state != AST_STREAM_STATE_REMOVED) { + ast_stream_set_formats(joint_stream, joint_cap); + } + } ao2_ref(joint_cap, -1); @@ -595,103 +1057,916 @@ static struct ast_stream *merge_streams(const struct ast_stream *local, } /*! - * \brief Get a local stream that corresponds with a remote stream. + * \internal + * \brief Determine if a merged topology should be rejected. + * \since 15.0.0 * - * \param local The local topology - * \param media_type The type of stream we are looking for - * \param[in,out] media_indices Keeps track of where to start searching in the topology + * \param topology What topology to determine if we reject * - * \retval -1 No corresponding stream found - * \retval index The corresponding stream index + * \retval 0 if not rejected. + * \retval non-zero if rejected. */ -static int get_corresponding_index(const struct ast_stream_topology *local, - enum ast_media_type media_type, int *media_indices) +static int sdp_topology_is_rejected(struct ast_stream_topology *topology) { - int i; + int idx; + struct ast_stream *stream; + + for (idx = ast_stream_topology_get_count(topology); idx--;) { + stream = ast_stream_topology_get_stream(topology, idx); + if (ast_stream_get_state(stream) != AST_STREAM_STATE_REMOVED) { + /* At least one stream is not declined */ + return 0; + } + } + + /* All streams are declined */ + return 1; +} + +static void sdp_state_stream_copy_common(struct sdp_state_stream *dst, const struct sdp_state_stream *src) +{ + ast_sockaddr_copy(&dst->connection_address, + &src->connection_address); + /* Explicitly does not copy the local or remote hold states. */ + dst->t38_local_params = src->t38_local_params; +} + +static void sdp_state_stream_copy(struct sdp_state_stream *dst, const struct sdp_state_stream *src) +{ + *dst = *src; + + switch (dst->type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + ao2_bump(dst->rtp); + break; + case AST_MEDIA_TYPE_IMAGE: + ao2_bump(dst->udptl); + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; + } +} - for (i = media_indices[media_type]; i < ast_stream_topology_get_count(local); ++i) { - struct ast_stream *candidate; +/*! + * \internal + * \brief Initialize an int vector and default the contents to the member index. + * \since 15.0.0 + * + * \param vect Vetctor to initialize and set to default values. + * \param size Size of the vector to setup. + * + * \retval 0 on success. + * \retval -1 on failure. + */ +static int sdp_vect_idx_init(struct ast_vector_int *vect, size_t size) +{ + int idx; - candidate = ast_stream_topology_get_stream(local, i); - if (ast_stream_get_type(candidate) == media_type) { - media_indices[media_type] = i + 1; - return i; + if (AST_VECTOR_INIT(vect, size)) { + return -1; + } + for (idx = 0; idx < size; ++idx) { + AST_VECTOR_APPEND(vect, idx); + } + return 0; +} + +/*! + * \internal + * \brief Compare stream types for sort order. + * \since 15.0.0 + * + * \param left Stream parameter on left + * \param right Stream parameter on right + * + * \retval <0 left stream sorts first. + * \retval =0 streams match. + * \retval >0 right stream sorts first. + */ +static int sdp_stream_cmp_by_type(const struct ast_stream *left, const struct ast_stream *right) +{ + enum ast_media_type left_type = ast_stream_get_type(left); + enum ast_media_type right_type = ast_stream_get_type(right); + + /* Treat audio and image as the same for T.38 support */ + if (left_type == AST_MEDIA_TYPE_IMAGE) { + left_type = AST_MEDIA_TYPE_AUDIO; + } + if (right_type == AST_MEDIA_TYPE_IMAGE) { + right_type = AST_MEDIA_TYPE_AUDIO; + } + + return left_type - right_type; +} + +/*! + * \internal + * \brief Compare stream names and types for sort order. + * \since 15.0.0 + * + * \param left Stream parameter on left + * \param right Stream parameter on right + * + * \retval <0 left stream sorts first. + * \retval =0 streams match. + * \retval >0 right stream sorts first. + */ +static int sdp_stream_cmp_by_name(const struct ast_stream *left, const struct ast_stream *right) +{ + int cmp; + const char *left_name; + + left_name = ast_stream_get_name(left); + cmp = strcmp(left_name, ast_stream_get_name(right)); + if (!cmp) { + cmp = sdp_stream_cmp_by_type(left, right); + if (!cmp) { + /* Are the stream names real or type names which aren't matchable? */ + if (ast_strlen_zero(left_name) + || !strcmp(left_name, ast_codec_media_type2str(ast_stream_get_type(left))) + || !strcmp(left_name, ast_codec_media_type2str(ast_stream_get_type(right)))) { + /* The streams don't actually have real names */ + cmp = -1; + } } } + return cmp; +} - /* No stream of the type left in the topology */ - media_indices[media_type] = i; - return -1; +/*! + * \internal + * \brief Merge topology streams by the match function. + * \since 15.0.0 + * + * \param sdp_state + * \param current_topology Topology to update with state. + * \param update_topology Topology to merge into the current topology. + * \param current_vect Stream index vector of remaining current_topology streams. + * \param update_vect Stream index vector of remaining update_topology streams. + * \param backfill_candidate Array of flags marking current_topology streams + * that can be reused for a different stream. + * \param match Stream comparison function to identify corresponding streams + * between the current_topology and update_topology. + * \param merged_topology Output topology of merged streams. + * \param compact_streams TRUE if backfill and limit number of streams. + * + * \retval 0 on success. + * \retval -1 on failure. + */ +static int sdp_merge_streams_match( + const struct ast_sdp_state *sdp_state, + const struct ast_stream_topology *current_topology, + const struct ast_stream_topology *update_topology, + struct ast_vector_int *current_vect, + struct ast_vector_int *update_vect, + char backfill_candidate[], + int (*match)(const struct ast_stream *left, const struct ast_stream *right), + struct ast_stream_topology *merged_topology, + int compact_streams) +{ + struct ast_stream *current_stream; + struct ast_stream *update_stream; + int current_idx; + int update_idx; + int idx; + + for (current_idx = 0; current_idx < AST_VECTOR_SIZE(current_vect);) { + idx = AST_VECTOR_GET(current_vect, current_idx); + current_stream = ast_stream_topology_get_stream(current_topology, idx); + + for (update_idx = 0; update_idx < AST_VECTOR_SIZE(update_vect); ++update_idx) { + idx = AST_VECTOR_GET(update_vect, update_idx); + update_stream = ast_stream_topology_get_stream(update_topology, idx); + + if (match(current_stream, update_stream)) { + continue; + } + + if (!compact_streams + || ast_stream_get_state(current_stream) != AST_STREAM_STATE_REMOVED + || ast_stream_get_state(update_stream) != AST_STREAM_STATE_REMOVED) { + struct ast_stream *merged_stream; + + merged_stream = merge_local_stream(sdp_state->options, update_stream); + if (!merged_stream) { + return -1; + } + idx = AST_VECTOR_GET(current_vect, current_idx); + ast_stream_topology_set_stream(merged_topology, idx, merged_stream); + + /* + * The current_stream cannot be considered a backfill_candidate + * anymore since it got updated. + * + * XXX It could be argued that if the declined status didn't + * change because the merged_stream became declined then we + * shouldn't remove the stream slot as a backfill_candidate + * and we shouldn't update the merged_topology stream. If we + * then backfilled the stream we would likely mess up the core + * if it is matching streams by type since the core attempted + * to update the stream with an incompatible stream. Any + * backfilled streams could cause a stream type ordering + * problem. However, we do need to reclaim declined stream + * slots sometime. + */ + backfill_candidate[idx] = 0; + } + + AST_VECTOR_REMOVE_ORDERED(current_vect, current_idx); + AST_VECTOR_REMOVE_ORDERED(update_vect, update_idx); + goto matched_next; + } + + ++current_idx; +matched_next:; + } + return 0; +} + +/*! + * \internal + * \brief Merge the current local topology with an updated topology. + * \since 15.0.0 + * + * \param sdp_state + * \param current_topology Topology to update with state. + * \param update_topology Topology to merge into the current topology. + * \param compact_streams TRUE if backfill and limit number of streams. + * + * \retval merged topology on success. + * \retval NULL on failure. + */ +static struct ast_stream_topology *merge_local_topologies( + const struct ast_sdp_state *sdp_state, + const struct ast_stream_topology *current_topology, + const struct ast_stream_topology *update_topology, + int compact_streams) +{ + struct ast_stream_topology *merged_topology; + struct ast_stream *current_stream; + struct ast_stream *update_stream; + struct ast_stream *merged_stream; + struct ast_vector_int current_vect; + struct ast_vector_int update_vect; + int current_idx = ast_stream_topology_get_count(current_topology); + int update_idx; + int idx; + char backfill_candidate[current_idx]; + + memset(backfill_candidate, 0, current_idx); + + if (compact_streams) { + /* Limit matching consideration to the maximum allowed live streams. */ + idx = ast_sdp_options_get_max_streams(sdp_state->options); + if (idx < current_idx) { + current_idx = idx; + } + } + if (sdp_vect_idx_init(¤t_vect, current_idx)) { + return NULL; + } + + if (sdp_vect_idx_init(&update_vect, ast_stream_topology_get_count(update_topology))) { + AST_VECTOR_FREE(¤t_vect); + return NULL; + } + + merged_topology = ast_stream_topology_clone(current_topology); + if (!merged_topology) { + goto fail; + } + + /* + * Remove any unsupported current streams from match consideration + * and mark potential backfill candidates. + */ + for (current_idx = AST_VECTOR_SIZE(¤t_vect); current_idx--;) { + idx = AST_VECTOR_GET(¤t_vect, current_idx); + current_stream = ast_stream_topology_get_stream(current_topology, idx); + if (ast_stream_get_state(current_stream) == AST_STREAM_STATE_REMOVED + && compact_streams) { + /* The declined stream is a potential backfill candidate */ + backfill_candidate[idx] = 1; + } + if (sdp_is_stream_type_supported(ast_stream_get_type(current_stream))) { + continue; + } + /* Unsupported current streams should always be declined */ + ast_assert(ast_stream_get_state(current_stream) == AST_STREAM_STATE_REMOVED); + + AST_VECTOR_REMOVE_ORDERED(¤t_vect, current_idx); + } + + /* Remove any unsupported update streams from match consideration. */ + for (update_idx = AST_VECTOR_SIZE(&update_vect); update_idx--;) { + idx = AST_VECTOR_GET(&update_vect, update_idx); + update_stream = ast_stream_topology_get_stream(update_topology, idx); + if (sdp_is_stream_type_supported(ast_stream_get_type(update_stream))) { + continue; + } + + AST_VECTOR_REMOVE_ORDERED(&update_vect, update_idx); + } + + /* Match by stream name and type */ + if (sdp_merge_streams_match(sdp_state, current_topology, update_topology, + ¤t_vect, &update_vect, backfill_candidate, sdp_stream_cmp_by_name, + merged_topology, compact_streams)) { + goto fail; + } + + /* Match by stream type */ + if (sdp_merge_streams_match(sdp_state, current_topology, update_topology, + ¤t_vect, &update_vect, backfill_candidate, sdp_stream_cmp_by_type, + merged_topology, compact_streams)) { + goto fail; + } + + /* Decline unmatched current stream slots */ + for (current_idx = AST_VECTOR_SIZE(¤t_vect); current_idx--;) { + idx = AST_VECTOR_GET(¤t_vect, current_idx); + current_stream = ast_stream_topology_get_stream(current_topology, idx); + + if (ast_stream_get_state(current_stream) == AST_STREAM_STATE_REMOVED) { + /* Stream is already declined. */ + continue; + } + + merged_stream = decline_stream(ast_stream_get_type(current_stream), + ast_stream_get_name(current_stream)); + if (!merged_stream) { + goto fail; + } + ast_stream_topology_set_stream(merged_topology, idx, merged_stream); + } + + /* Backfill new update stream slots into pre-existing declined current stream slots */ + while (AST_VECTOR_SIZE(&update_vect)) { + idx = ast_stream_topology_get_count(current_topology); + for (current_idx = 0; current_idx < idx; ++current_idx) { + if (backfill_candidate[current_idx]) { + break; + } + } + if (idx <= current_idx) { + /* No more backfill candidates remain. */ + break; + } + /* There should only be backfill stream slots when we are compact_streams */ + ast_assert(compact_streams); + + idx = AST_VECTOR_GET(&update_vect, 0); + update_stream = ast_stream_topology_get_stream(update_topology, idx); + AST_VECTOR_REMOVE_ORDERED(&update_vect, 0); + + if (ast_stream_get_state(update_stream) == AST_STREAM_STATE_REMOVED) { + /* New stream is already declined so don't bother adding it. */ + continue; + } + + merged_stream = merge_local_stream(sdp_state->options, update_stream); + if (!merged_stream) { + goto fail; + } + if (ast_stream_get_state(merged_stream) == AST_STREAM_STATE_REMOVED) { + /* New stream not compatible so don't bother adding it. */ + ast_stream_free(merged_stream); + continue; + } + + /* Add the new stream into the backfill stream slot. */ + ast_stream_topology_set_stream(merged_topology, current_idx, merged_stream); + backfill_candidate[current_idx] = 0; + } + + /* Append any remaining new update stream slots that can fit. */ + while (AST_VECTOR_SIZE(&update_vect) + && (!compact_streams + || ast_stream_topology_get_count(merged_topology) + < ast_sdp_options_get_max_streams(sdp_state->options))) { + idx = AST_VECTOR_GET(&update_vect, 0); + update_stream = ast_stream_topology_get_stream(update_topology, idx); + AST_VECTOR_REMOVE_ORDERED(&update_vect, 0); + + if (ast_stream_get_state(update_stream) == AST_STREAM_STATE_REMOVED) { + /* New stream is already declined so don't bother adding it. */ + continue; + } + + merged_stream = merge_local_stream(sdp_state->options, update_stream); + if (!merged_stream) { + goto fail; + } + if (ast_stream_get_state(merged_stream) == AST_STREAM_STATE_REMOVED) { + /* New stream not compatible so don't bother adding it. */ + ast_stream_free(merged_stream); + continue; + } + + /* Append the new update stream. */ + if (ast_stream_topology_append_stream(merged_topology, merged_stream) < 0) { + ast_stream_free(merged_stream); + goto fail; + } + } + + AST_VECTOR_FREE(¤t_vect); + AST_VECTOR_FREE(&update_vect); + return merged_topology; + +fail: + ast_stream_topology_free(merged_topology); + AST_VECTOR_FREE(¤t_vect); + AST_VECTOR_FREE(&update_vect); + return NULL; } /*! - * XXX TODO The merge_capabilities() function needs to be split into - * merging for new local topologies and new remote topologies. Also - * the possibility of changing the stream types needs consideration. - * Audio to video may or may not need us to keep the same RTP instance - * because the stream position is still RTP. A new RTP instance would - * cause us to change ports. Audio to image is definitely going to - * happen for T.38. - * - * A new remote topology as an initial offer needs to dictate the - * number of streams and the order. As a sdp_state option we may - * allow creation of new active streams not defined by the current - * local topology. A subsequent remote offer can change the stream - * types and add streams. The sdp_state option could regulate - * creation of new active streams here as well. An answer cannot - * change stream types or the number of streams but can decline - * streams. Any attempt to do so should report an error and possibly - * disconnect the call. - * - * A local topology update needs to be merged differently. It cannot - * reduce the number of streams already defined without violating the - * SDP RFC. The local merge could take the new topology stream - * verbatim and add declined streams to fill out any shortfall with - * the exiting topology. This strategy is needed if we want to change - * an audio stream to an image stream for T.38 fax and vice versa. - * The local merge could take the new topology and map the streams to - * the existing local topology. The new topology stream format caps - * would be copied into the merged topology so we could change what - * codecs are negotiated. + * \internal + * \brief Remove declined streams appended beyond orig_topology. + * \since 15.0.0 + * + * \param sdp_state + * \param orig_topology Negotiated or initial topology. + * \param new_topology New proposed topology. + * + * \return Nothing */ +static void remove_appended_declined_streams(const struct ast_sdp_state *sdp_state, + const struct ast_stream_topology *orig_topology, + struct ast_stream_topology *new_topology) +{ + struct ast_stream *stream; + int orig_count; + int idx; + + orig_count = ast_stream_topology_get_count(orig_topology); + for (idx = ast_stream_topology_get_count(new_topology); orig_count < idx;) { + --idx; + stream = ast_stream_topology_get_stream(new_topology, idx); + if (ast_stream_get_state(stream) != AST_STREAM_STATE_REMOVED) { + continue; + } + ast_stream_topology_del_stream(new_topology, idx); + } +} + /*! - * \brief Merge existing stream capabilities and a new topology into joint capabilities. + * \internal + * \brief Setup a new state stream from a possibly existing state stream. + * \since 15.0.0 + * + * \param sdp_state + * \param new_state_stream What state stream to setup + * \param old_state_stream Source of previous state stream information. + * May be NULL. + * \param new_type Type of the new state stream. + * + * \retval 0 on success. + * \retval -1 on failure. + */ +static int setup_new_stream_capabilities( + const struct ast_sdp_state *sdp_state, + struct sdp_state_stream *new_state_stream, + struct sdp_state_stream *old_state_stream, + enum ast_media_type new_type) +{ + if (old_state_stream) { + /* + * Copy everything potentially useful for a new stream state type + * from the old stream of a possible different type. + */ + sdp_state_stream_copy_common(new_state_stream, old_state_stream); + /* We also need to preserve the locally_held state for the new stream. */ + new_state_stream->locally_held = old_state_stream->locally_held; + } + new_state_stream->type = new_type; + + switch (new_type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + new_state_stream->rtp = create_rtp(sdp_state->options, new_type); + if (!new_state_stream->rtp) { + return -1; + } + break; + case AST_MEDIA_TYPE_IMAGE: + new_state_stream->udptl = create_udptl(sdp_state->options); + if (!new_state_stream->udptl) { + return -1; + } + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; + } + return 0; +} + +/*! + * \brief Merge existing stream capabilities and a new topology. * * \param sdp_state The state needing capabilities merged - * \param new_topology The new topology to base merged capabilities on - * \param is_local If new_topology is a local update. + * \param new_topology The topology to merge with our proposed capabilities * * \details + * * This is a bit complicated. The idea is that we already have some * capabilities set, and we've now been confronted with a new stream - * topology. We want to take what's been presented to us and merge - * those new capabilities with our own. - * - * For each of the new streams, we try to find a corresponding stream - * in our proposed capabilities. If we find one, then we get the - * compatible formats of the two streams and create a new stream with - * those formats set. We then will re-use the underlying media - * instance (such as an RTP instance) on this merged stream. - * - * The is_local parameter determines whether we should attempt to - * create new media instances. If we do not find a corresponding - * stream, then we create a new one. If the is_local parameter is - * true, this created stream is made a clone of the new stream, and a - * media instance is created. If the is_local parameter is not true, - * then the created stream has no formats set and no media instance is - * created for it. + * topology from the system. We want to take what we had before and + * merge them with the new topology from the system. + * + * According to the RFC, stream slots can change their types only if + * they are carrying the same logical information or an offer is + * reusing a declined slot or new stream slots are added to the end + * of the list. Switching a stream from audio to T.38 makes sense + * because the stream slot is carrying the same information just in a + * different format. + * + * We can setup new streams offered by the system up to our + * configured maximum stream slots. New stream slots requested over + * the maximum are discarded. + * + * \retval NULL An error occurred + * \retval non-NULL The merged capabilities + */ +static struct sdp_state_capabilities *merge_local_capabilities( + const struct ast_sdp_state *sdp_state, + const struct ast_stream_topology *new_topology) +{ + const struct sdp_state_capabilities *current = sdp_state->proposed_capabilities; + struct sdp_state_capabilities *merged_capabilities; + int idx; + + ast_assert(current != NULL); + + merged_capabilities = ast_calloc(1, sizeof(*merged_capabilities)); + if (!merged_capabilities) { + return NULL; + } + + merged_capabilities->topology = merge_local_topologies(sdp_state, current->topology, + new_topology, 1); + if (!merged_capabilities->topology) { + goto fail; + } + sdp_state_cb_offerer_modify_topology(sdp_state, merged_capabilities->topology); + remove_appended_declined_streams(sdp_state, current->topology, + merged_capabilities->topology); + + if (AST_VECTOR_INIT(&merged_capabilities->streams, + ast_stream_topology_get_count(merged_capabilities->topology))) { + goto fail; + } + + for (idx = 0; idx < ast_stream_topology_get_count(merged_capabilities->topology); ++idx) { + struct sdp_state_stream *merged_state_stream; + struct sdp_state_stream *current_state_stream; + struct ast_stream *merged_stream; + struct ast_stream *current_stream; + enum ast_media_type merged_stream_type; + enum ast_media_type current_stream_type; + + merged_state_stream = ast_calloc(1, sizeof(*merged_state_stream)); + if (!merged_state_stream) { + goto fail; + } + + merged_stream = ast_stream_topology_get_stream(merged_capabilities->topology, idx); + merged_stream_type = ast_stream_get_type(merged_stream); + + if (idx < ast_stream_topology_get_count(current->topology)) { + current_state_stream = AST_VECTOR_GET(¤t->streams, idx); + current_stream = ast_stream_topology_get_stream(current->topology, idx); + current_stream_type = ast_stream_get_type(current_stream); + } else { + /* The merged topology is adding a stream */ + current_state_stream = NULL; + current_stream = NULL; + current_stream_type = AST_MEDIA_TYPE_UNKNOWN; + } + + if (ast_stream_get_state(merged_stream) == AST_STREAM_STATE_REMOVED) { + if (current_state_stream) { + /* Copy everything potentially useful to a declined stream state. */ + sdp_state_stream_copy_common(merged_state_stream, current_state_stream); + } + merged_state_stream->type = merged_stream_type; + } else if (!current_stream + || ast_stream_get_state(current_stream) == AST_STREAM_STATE_REMOVED) { + /* This is a new stream */ + if (setup_new_stream_capabilities(sdp_state, merged_state_stream, + current_state_stream, merged_stream_type)) { + sdp_state_stream_free(merged_state_stream); + goto fail; + } + } else if (merged_stream_type == current_stream_type) { + /* Stream type is not changing. */ + sdp_state_stream_copy(merged_state_stream, current_state_stream); + } else { + /* + * Stream type is changing. Need to replace the stream. + * + * Unsupported streams should already be handled earlier because + * they are always declined. + */ + ast_assert(sdp_is_stream_type_supported(merged_stream_type)); + + /* + * XXX We might need to keep the old RTP instance if the new + * stream type is also RTP. We would just be changing between + * audio and video in that case. However we will create a new + * RTP instance anyway since its purpose has to be changing. + * Any RTP packets in flight from the old stream type might + * cause mischief. + */ + if (setup_new_stream_capabilities(sdp_state, merged_state_stream, + current_state_stream, merged_stream_type)) { + sdp_state_stream_free(merged_state_stream); + goto fail; + } + } + + if (AST_VECTOR_APPEND(&merged_capabilities->streams, merged_state_stream)) { + sdp_state_stream_free(merged_state_stream); + goto fail; + } + } + + return merged_capabilities; + +fail: + sdp_state_capabilities_free(merged_capabilities); + return NULL; +} + +static void merge_remote_stream_capabilities( + const struct ast_sdp_state *sdp_state, + struct sdp_state_stream *joint_state_stream, + struct sdp_state_stream *local_state_stream, + struct ast_stream *remote_stream) +{ + struct ast_rtp_codecs *codecs; + + *joint_state_stream = *local_state_stream; + /* + * Need to explicitly set the type to the remote because we could + * be changing the type between audio and video. + */ + joint_state_stream->type = ast_stream_get_type(remote_stream); + + switch (joint_state_stream->type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + ao2_bump(joint_state_stream->rtp); + codecs = ast_stream_get_data(remote_stream, AST_STREAM_DATA_RTP_CODECS); + ast_assert(codecs != NULL); + if (sdp_state->role == SDP_ROLE_ANSWERER) { + /* + * Setup rx payload type mapping to prefer the mapping + * from the peer that the RFC says we SHOULD use. + */ + ast_rtp_codecs_payloads_xover(codecs, codecs, NULL); + } + ast_rtp_codecs_payloads_copy(codecs, + ast_rtp_instance_get_codecs(joint_state_stream->rtp->instance), + joint_state_stream->rtp->instance); + break; + case AST_MEDIA_TYPE_IMAGE: + joint_state_stream->udptl = ao2_bump(joint_state_stream->udptl); + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; + } +} + +static int create_remote_stream_capabilities( + const struct ast_sdp_state *sdp_state, + struct sdp_state_stream *joint_state_stream, + struct sdp_state_stream *local_state_stream, + struct ast_stream *remote_stream) +{ + struct ast_rtp_codecs *codecs; + + /* We can only create streams if we are the answerer */ + ast_assert(sdp_state->role == SDP_ROLE_ANSWERER); + + if (local_state_stream) { + /* + * Copy everything potentially useful for a new stream state type + * from the old stream of a possible different type. + */ + sdp_state_stream_copy_common(joint_state_stream, local_state_stream); + /* We also need to preserve the locally_held state for the new stream. */ + joint_state_stream->locally_held = local_state_stream->locally_held; + } + joint_state_stream->type = ast_stream_get_type(remote_stream); + + switch (joint_state_stream->type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + joint_state_stream->rtp = create_rtp(sdp_state->options, joint_state_stream->type); + if (!joint_state_stream->rtp) { + return -1; + } + + /* + * Setup rx payload type mapping to prefer the mapping + * from the peer that the RFC says we SHOULD use. + */ + codecs = ast_stream_get_data(remote_stream, AST_STREAM_DATA_RTP_CODECS); + ast_assert(codecs != NULL); + ast_rtp_codecs_payloads_xover(codecs, codecs, NULL); + ast_rtp_codecs_payloads_copy(codecs, + ast_rtp_instance_get_codecs(joint_state_stream->rtp->instance), + joint_state_stream->rtp->instance); + break; + case AST_MEDIA_TYPE_IMAGE: + joint_state_stream->udptl = create_udptl(sdp_state->options); + if (!joint_state_stream->udptl) { + return -1; + } + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; + } + return 0; +} + +/*! + * \internal + * \brief Create a joint topology from the remote topology. + * \since 15.0.0 + * + * \param sdp_state The state needing capabilities merged. + * \param local Capabilities to merge the remote topology into. + * \param remote_topology The topology to merge with our local capabilities. + * + * \retval joint topology on success. + * \retval NULL on failure. + */ +static struct ast_stream_topology *merge_remote_topology( + const struct ast_sdp_state *sdp_state, + const struct sdp_state_capabilities *local, + const struct ast_stream_topology *remote_topology) +{ + struct ast_stream_topology *joint_topology; + int idx; + + joint_topology = ast_stream_topology_alloc(); + if (!joint_topology) { + return NULL; + } + + for (idx = 0; idx < ast_stream_topology_get_count(remote_topology); ++idx) { + enum ast_media_type local_stream_type; + enum ast_media_type remote_stream_type; + struct ast_stream *remote_stream; + struct ast_stream *local_stream; + struct ast_stream *joint_stream; + struct sdp_state_stream *local_state_stream; + + remote_stream = ast_stream_topology_get_stream(remote_topology, idx); + remote_stream_type = ast_stream_get_type(remote_stream); + + if (idx < ast_stream_topology_get_count(local->topology)) { + local_state_stream = AST_VECTOR_GET(&local->streams, idx); + local_stream = ast_stream_topology_get_stream(local->topology, idx); + local_stream_type = ast_stream_get_type(local_stream); + } else { + /* The remote is adding a stream slot */ + local_state_stream = NULL; + local_stream = NULL; + local_stream_type = AST_MEDIA_TYPE_UNKNOWN; + + if (sdp_state->role != SDP_ROLE_ANSWERER) { + /* Remote cannot add a new stream slot in an answer SDP */ + ast_debug(1, + "Bad. Ignoring new %s stream slot remote answer SDP trying to add.\n", + ast_codec_media_type2str(remote_stream_type)); + continue; + } + } + + if (local_stream + && ast_stream_get_state(local_stream) != AST_STREAM_STATE_REMOVED) { + if (remote_stream_type == local_stream_type) { + /* Stream type is not changing. */ + joint_stream = merge_remote_stream(sdp_state, local_stream, + local_state_stream->locally_held, remote_stream); + } else if (sdp_state->role == SDP_ROLE_ANSWERER) { + /* Stream type is changing. */ + joint_stream = merge_remote_stream(sdp_state, NULL, + local_state_stream->locally_held, remote_stream); + } else { + /* + * Remote cannot change the stream type we offered. + * Mark as declined. + */ + ast_debug(1, + "Bad. Remote answer SDP trying to change the stream type from %s to %s.\n", + ast_codec_media_type2str(local_stream_type), + ast_codec_media_type2str(remote_stream_type)); + joint_stream = decline_stream(local_stream_type, + ast_stream_get_name(local_stream)); + } + } else { + /* Local stream is either dead/declined or nonexistent. */ + if (sdp_state->role == SDP_ROLE_ANSWERER) { + if (sdp_is_stream_type_supported(remote_stream_type) + && ast_stream_get_state(remote_stream) != AST_STREAM_STATE_REMOVED + && idx < ast_sdp_options_get_max_streams(sdp_state->options)) { + /* Try to create the new stream */ + joint_stream = merge_remote_stream(sdp_state, NULL, + local_state_stream ? local_state_stream->locally_held : 0, + remote_stream); + } else { + const char *stream_name; + + /* Decline the remote stream. */ + if (local_stream + && local_stream_type == remote_stream_type) { + /* Preserve the previous stream name */ + stream_name = ast_stream_get_name(local_stream); + } else { + stream_name = NULL; + } + joint_stream = decline_stream(remote_stream_type, stream_name); + } + } else { + /* Decline the stream. */ + if (DEBUG_ATLEAST(1) + && ast_stream_get_state(remote_stream) != AST_STREAM_STATE_REMOVED) { + /* + * Remote cannot request a new stream in place of a declined + * stream in an answer SDP. + */ + ast_log(LOG_DEBUG, + "Bad. Remote answer SDP trying to use a declined stream slot for %s.\n", + ast_codec_media_type2str(remote_stream_type)); + } + joint_stream = decline_stream(local_stream_type, + ast_stream_get_name(local_stream)); + } + } + + if (!joint_stream) { + goto fail; + } + if (ast_stream_topology_append_stream(joint_topology, joint_stream) < 0) { + ast_stream_free(joint_stream); + goto fail; + } + } + + return joint_topology; + +fail: + ast_stream_topology_free(joint_topology); + return NULL; +} + +/*! + * \brief Merge our stream capabilities and a remote topology into joint capabilities. + * + * \param sdp_state The state needing capabilities merged + * \param remote_topology The topology to merge with our proposed capabilities + * + * \details + * This is a bit complicated. The idea is that we already have some + * capabilities set, and we've now been confronted with a stream + * topology from the remote end. We want to take what's been + * presented to us and merge those new capabilities with our own. + * + * According to the RFC, stream slots can change their types only if + * they are carrying the same logical information or an offer is + * reusing a declined slot or new stream slots are added to the end + * of the list. Switching a stream from audio to T.38 makes sense + * because the stream slot is carrying the same information just in a + * different format. + * + * When we are the answerer we can setup new streams offered by the + * remote up to our configured maximum stream slots. New stream + * slots offered over the maximum are unconditionally declined. * * \retval NULL An error occurred * \retval non-NULL The merged capabilities */ -static struct sdp_state_capabilities *merge_capabilities(const struct ast_sdp_state *sdp_state, - const struct ast_stream_topology *new_topology, int is_local) +static struct sdp_state_capabilities *merge_remote_capabilities( + const struct ast_sdp_state *sdp_state, + const struct ast_stream_topology *remote_topology) { const struct sdp_state_capabilities *local = sdp_state->proposed_capabilities; struct sdp_state_capabilities *joint_capabilities; - int media_indices[AST_MEDIA_TYPE_END] = {0}; - int i; - static const char dummy_name[] = "dummy"; + int idx; ast_assert(local != NULL); @@ -700,150 +1975,131 @@ static struct sdp_state_capabilities *merge_capabilities(const struct ast_sdp_st return NULL; } - joint_capabilities->topology = ast_stream_topology_alloc(); + joint_capabilities->topology = merge_remote_topology(sdp_state, local, remote_topology); if (!joint_capabilities->topology) { goto fail; } - if (AST_VECTOR_INIT(&joint_capabilities->streams, AST_VECTOR_SIZE(&local->streams))) { + if (sdp_state->role == SDP_ROLE_ANSWERER) { + sdp_state_cb_answerer_modify_topology(sdp_state, joint_capabilities->topology); + } + idx = ast_stream_topology_get_count(joint_capabilities->topology); + if (AST_VECTOR_INIT(&joint_capabilities->streams, idx)) { goto fail; } - ast_sockaddr_copy(&joint_capabilities->connection_address, &local->connection_address); - for (i = 0; i < ast_stream_topology_get_count(new_topology); ++i) { - enum ast_media_type new_stream_type; - struct ast_stream *new_stream; + for (idx = 0; idx < ast_stream_topology_get_count(remote_topology); ++idx) { + enum ast_media_type local_stream_type; + enum ast_media_type remote_stream_type; + struct ast_stream *remote_stream; struct ast_stream *local_stream; struct ast_stream *joint_stream; + struct sdp_state_stream *local_state_stream; struct sdp_state_stream *joint_state_stream; - int local_index; joint_state_stream = ast_calloc(1, sizeof(*joint_state_stream)); if (!joint_state_stream) { goto fail; } - new_stream = ast_stream_topology_get_stream(new_topology, i); - new_stream_type = ast_stream_get_type(new_stream); + remote_stream = ast_stream_topology_get_stream(remote_topology, idx); + remote_stream_type = ast_stream_get_type(remote_stream); - local_index = get_corresponding_index(local->topology, new_stream_type, media_indices); - if (0 <= local_index) { - local_stream = ast_stream_topology_get_stream(local->topology, local_index); - if (!strcmp(ast_stream_get_name(local_stream), dummy_name)) { - /* The local stream is a non-exixtent dummy stream. */ - local_stream = NULL; - } + if (idx < ast_stream_topology_get_count(local->topology)) { + local_state_stream = AST_VECTOR_GET(&local->streams, idx); + local_stream = ast_stream_topology_get_stream(local->topology, idx); + local_stream_type = ast_stream_get_type(local_stream); } else { + /* The remote is adding a stream slot */ + local_state_stream = NULL; local_stream = NULL; - } - if (local_stream) { - struct sdp_state_stream *local_state_stream; - struct ast_rtp_codecs *codecs; + local_stream_type = AST_MEDIA_TYPE_UNKNOWN; - if (is_local) { - /* Replace the local stream with the new local stream. */ - joint_stream = ast_stream_clone(new_stream, NULL); - } else { - joint_stream = merge_streams(local_stream, new_stream); - } - if (!joint_stream) { + if (sdp_state->role != SDP_ROLE_ANSWERER) { + /* Remote cannot add a new stream slot in an answer SDP */ sdp_state_stream_free(joint_state_stream); - goto fail; - } - - local_state_stream = AST_VECTOR_GET(&local->streams, local_index); - joint_state_stream->type = local_state_stream->type; - - switch (joint_state_stream->type) { - case AST_MEDIA_TYPE_AUDIO: - case AST_MEDIA_TYPE_VIDEO: - joint_state_stream->rtp = ao2_bump(local_state_stream->rtp); - if (is_local) { - break; - } - codecs = ast_stream_get_data(new_stream, AST_STREAM_DATA_RTP_CODECS); - ast_assert(codecs != NULL); - if (sdp_state->role == SDP_ROLE_ANSWERER) { - /* - * Setup rx payload type mapping to prefer the mapping - * from the peer that the RFC says we SHOULD use. - */ - ast_rtp_codecs_payloads_xover(codecs, codecs, NULL); - } - ast_rtp_codecs_payloads_copy(codecs, - ast_rtp_instance_get_codecs(joint_state_stream->rtp->instance), - joint_state_stream->rtp->instance); - break; - case AST_MEDIA_TYPE_IMAGE: - joint_state_stream->udptl = ao2_bump(local_state_stream->udptl); - joint_state_stream->t38_local_params = local_state_stream->t38_local_params; - break; - case AST_MEDIA_TYPE_UNKNOWN: - case AST_MEDIA_TYPE_TEXT: - case AST_MEDIA_TYPE_END: break; } + } - if (!ast_sockaddr_isnull(&local_state_stream->connection_address)) { - ast_sockaddr_copy(&joint_state_stream->connection_address, - &local_state_stream->connection_address); + joint_stream = ast_stream_topology_get_stream(joint_capabilities->topology, + idx); + + if (local_stream + && ast_stream_get_state(local_stream) != AST_STREAM_STATE_REMOVED) { + if (ast_stream_get_state(joint_stream) == AST_STREAM_STATE_REMOVED) { + /* Copy everything potentially useful to a declined stream state. */ + sdp_state_stream_copy_common(joint_state_stream, local_state_stream); + + joint_state_stream->type = ast_stream_get_type(joint_stream); + } else if (remote_stream_type == local_stream_type) { + /* Stream type is not changing. */ + merge_remote_stream_capabilities(sdp_state, joint_state_stream, + local_state_stream, remote_stream); + ast_assert(joint_state_stream->type == ast_stream_get_type(joint_stream)); } else { - ast_sockaddr_setnull(&joint_state_stream->connection_address); - } - joint_state_stream->locally_held = local_state_stream->locally_held; - } else if (is_local) { - /* We don't have a stream state that corresponds to the stream in the new topology, so - * create a stream state as appropriate. - */ - joint_stream = ast_stream_clone(new_stream, NULL); - if (!joint_stream) { - sdp_state_stream_free(joint_state_stream); - goto fail; - } - - switch (new_stream_type) { - case AST_MEDIA_TYPE_AUDIO: - case AST_MEDIA_TYPE_VIDEO: - joint_state_stream->rtp = create_rtp(sdp_state->options, - new_stream_type); - if (!joint_state_stream->rtp) { - ast_stream_free(joint_stream); - sdp_state_stream_free(joint_state_stream); - goto fail; - } - break; - case AST_MEDIA_TYPE_IMAGE: - joint_state_stream->udptl = create_udptl(sdp_state->options); - if (!joint_state_stream->udptl) { - ast_stream_free(joint_stream); + /* + * Stream type is changing. Need to replace the stream. + * + * XXX We might need to keep the old RTP instance if the new + * stream type is also RTP. We would just be changing between + * audio and video in that case. However we will create a new + * RTP instance anyway since its purpose has to be changing. + * Any RTP packets in flight from the old stream type might + * cause mischief. + */ + if (create_remote_stream_capabilities(sdp_state, joint_state_stream, + local_state_stream, remote_stream)) { sdp_state_stream_free(joint_state_stream); goto fail; } - break; - case AST_MEDIA_TYPE_UNKNOWN: - case AST_MEDIA_TYPE_TEXT: - case AST_MEDIA_TYPE_END: - break; + ast_assert(joint_state_stream->type == ast_stream_get_type(joint_stream)); } - ast_sockaddr_setnull(&joint_state_stream->connection_address); - joint_state_stream->locally_held = 0; } else { - /* We don't have a stream that corresponds to the stream in the new topology. Create a - * dummy stream to go in its place so that the resulting SDP created will contain - * the stream but will have no port or codecs set - */ - joint_stream = ast_stream_alloc(dummy_name, new_stream_type); - if (!joint_stream) { - sdp_state_stream_free(joint_state_stream); - goto fail; + /* Local stream is either dead/declined or nonexistent. */ + if (sdp_state->role == SDP_ROLE_ANSWERER) { + if (ast_stream_get_state(joint_stream) == AST_STREAM_STATE_REMOVED) { + if (local_state_stream) { + /* Copy everything potentially useful to a declined stream state. */ + sdp_state_stream_copy_common(joint_state_stream, local_state_stream); + } + joint_state_stream->type = ast_stream_get_type(joint_stream); + } else { + /* Try to create the new stream */ + if (create_remote_stream_capabilities(sdp_state, joint_state_stream, + local_state_stream, remote_stream)) { + sdp_state_stream_free(joint_state_stream); + goto fail; + } + ast_assert(joint_state_stream->type == ast_stream_get_type(joint_stream)); + } + } else { + /* Decline the stream. */ + ast_assert(ast_stream_get_state(joint_stream) == AST_STREAM_STATE_REMOVED); + if (local_state_stream) { + /* Copy everything potentially useful to a declined stream state. */ + sdp_state_stream_copy_common(joint_state_stream, local_state_stream); + } + joint_state_stream->type = ast_stream_get_type(joint_stream); } } - if (ast_stream_topology_append_stream(joint_capabilities->topology, joint_stream) < 0) { - ast_stream_free(joint_stream); - sdp_state_stream_free(joint_state_stream); - goto fail; + /* Determine if the remote placed the stream on hold. */ + joint_state_stream->remotely_held = 0; + if (ast_stream_get_state(joint_stream) != AST_STREAM_STATE_REMOVED) { + enum ast_stream_state remote_state; + + remote_state = ast_stream_get_state(remote_stream); + switch (remote_state) { + case AST_STREAM_STATE_INACTIVE: + case AST_STREAM_STATE_SENDONLY: + joint_state_stream->remotely_held = 1; + break; + default: + break; + } } + if (AST_VECTOR_APPEND(&joint_capabilities->streams, joint_state_stream)) { sdp_state_stream_free(joint_state_stream); goto fail; @@ -998,11 +2254,6 @@ static void update_rtp_after_merge(const struct ast_sdp_state *state, struct ast_sdp_c_line *c_line; struct ast_sockaddr *addrs; - if (!rtp) { - /* This is a dummy stream */ - return; - } - c_line = remote_m_line->c_line; if (!c_line) { c_line = remote_sdp->c_line; @@ -1058,11 +2309,6 @@ static void update_udptl_after_merge(const struct ast_sdp_state *state, struct s unsigned int fax_max_datagram; struct ast_sockaddr *addrs; - if (!udptl) { - /* This is a dummy stream */ - return; - } - a_line = ast_sdp_m_find_attribute(remote_m_line, "t38faxmaxdatagram", -1); if (!a_line) { a_line = ast_sdp_m_find_attribute(remote_m_line, "t38maxdatagram", -1); @@ -1102,6 +2348,49 @@ static void update_udptl_after_merge(const struct ast_sdp_state *state, struct s } } +static void sdp_apply_negotiated_state(struct ast_sdp_state *sdp_state) +{ + struct sdp_state_capabilities *capabilities = sdp_state->negotiated_capabilities; + int idx; + + if (!capabilities) { + /* Nothing to apply */ + return; + } + + sdp_state_cb_preapply_topology(sdp_state, capabilities->topology); + for (idx = 0; idx < AST_VECTOR_SIZE(&capabilities->streams); ++idx) { + struct sdp_state_stream *state_stream; + struct ast_stream *stream; + + stream = ast_stream_topology_get_stream(capabilities->topology, idx); + if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) { + /* Stream is declined */ + continue; + } + + state_stream = AST_VECTOR_GET(&capabilities->streams, idx); + switch (ast_stream_get_type(stream)) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + update_rtp_after_merge(sdp_state, state_stream->rtp, sdp_state->options, + sdp_state->remote_sdp, ast_sdp_get_m(sdp_state->remote_sdp, idx)); + break; + case AST_MEDIA_TYPE_IMAGE: + update_udptl_after_merge(sdp_state, state_stream->udptl, sdp_state->options, + sdp_state->remote_sdp, ast_sdp_get_m(sdp_state->remote_sdp, idx)); + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + /* All unsupported streams are declined */ + ast_assert(0); + break; + } + } + sdp_state_cb_postapply_topology(sdp_state, capabilities->topology); +} + static void set_negotiated_capabilities(struct ast_sdp_state *sdp_state, struct sdp_state_capabilities *new_capabilities) { @@ -1120,6 +2409,81 @@ static void set_proposed_capabilities(struct ast_sdp_state *sdp_state, sdp_state_capabilities_free(old_capabilities); } +/*! + * \internal + * \brief Copy the new capabilities into the proposed capabilities. + * \since 15.0.0 + * + * \param sdp_state The current SDP state + * \param new_capabilities Capabilities to copy + * + * \retval 0 on success. + * \retval -1 on failure. + */ +static int update_proposed_capabilities(struct ast_sdp_state *sdp_state, + struct sdp_state_capabilities *new_capabilities) +{ + struct sdp_state_capabilities *proposed_capabilities; + int idx; + + proposed_capabilities = ast_calloc(1, sizeof(*proposed_capabilities)); + if (!proposed_capabilities) { + return -1; + } + + proposed_capabilities->topology = ast_stream_topology_clone(new_capabilities->topology); + if (!proposed_capabilities->topology) { + goto fail; + } + + if (AST_VECTOR_INIT(&proposed_capabilities->streams, + AST_VECTOR_SIZE(&new_capabilities->streams))) { + goto fail; + } + + for (idx = 0; idx < AST_VECTOR_SIZE(&new_capabilities->streams); ++idx) { + struct sdp_state_stream *proposed_state_stream; + struct sdp_state_stream *new_state_stream; + + proposed_state_stream = ast_calloc(1, sizeof(*proposed_state_stream)); + if (!proposed_state_stream) { + goto fail; + } + + new_state_stream = AST_VECTOR_GET(&new_capabilities->streams, idx); + *proposed_state_stream = *new_state_stream; + + switch (proposed_state_stream->type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + ao2_bump(proposed_state_stream->rtp); + break; + case AST_MEDIA_TYPE_IMAGE: + ao2_bump(proposed_state_stream->udptl); + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; + } + + /* This is explicitly never set on the proposed capabilities struct */ + proposed_state_stream->remotely_held = 0; + + if (AST_VECTOR_APPEND(&proposed_capabilities->streams, proposed_state_stream)) { + sdp_state_stream_free(proposed_state_stream); + goto fail; + } + } + + set_proposed_capabilities(sdp_state, proposed_capabilities); + return 0; + +fail: + sdp_state_capabilities_free(proposed_capabilities); + return -1; +} + static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_state, const struct sdp_state_capabilities *capabilities); @@ -1127,65 +2491,47 @@ static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_sta * \brief Merge SDPs into a joint SDP. * * This function is used to take a remote SDP and merge it with our local - * capabilities to produce a new local SDP. After creating the new local SDP, - * it then iterates through media instances and updates them as necessary. For + * capabilities to produce a new local SDP. After creating the new local SDP, + * it then iterates through media instances and updates them as necessary. For * instance, if a specific RTP feature is supported by both us and the far end, * then we can ensure that the feature is enabled. * * \param sdp_state The current SDP state - * \retval -1 Failure + * * \retval 0 Success + * \retval -1 Failure + * Use ast_sdp_state_is_offer_rejected() to see if the offer SDP was rejected. */ static int merge_sdps(struct ast_sdp_state *sdp_state, const struct ast_sdp *remote_sdp) { struct sdp_state_capabilities *joint_capabilities; struct ast_stream_topology *remote_capabilities; - int i; remote_capabilities = ast_get_topology_from_sdp(remote_sdp, - sdp_state->options->g726_non_standard); + ast_sdp_options_get_g726_non_standard(sdp_state->options)); if (!remote_capabilities) { return -1; } - joint_capabilities = merge_capabilities(sdp_state, remote_capabilities, 0); + joint_capabilities = merge_remote_capabilities(sdp_state, remote_capabilities); ast_stream_topology_free(remote_capabilities); if (!joint_capabilities) { return -1; } - set_negotiated_capabilities(sdp_state, joint_capabilities); - - if (sdp_state->local_sdp) { - ast_sdp_free(sdp_state->local_sdp); - sdp_state->local_sdp = NULL; - } - - sdp_state->local_sdp = sdp_create_from_state(sdp_state, joint_capabilities); - if (!sdp_state->local_sdp) { - return -1; + if (sdp_state->role == SDP_ROLE_ANSWERER) { + sdp_state->remote_offer_rejected = + sdp_topology_is_rejected(joint_capabilities->topology) ? 1 : 0; + if (sdp_state->remote_offer_rejected) { + sdp_state_capabilities_free(joint_capabilities); + return -1; + } } + set_negotiated_capabilities(sdp_state, joint_capabilities); - for (i = 0; i < AST_VECTOR_SIZE(&joint_capabilities->streams); ++i) { - struct sdp_state_stream *state_stream; - - state_stream = AST_VECTOR_GET(&joint_capabilities->streams, i); + ao2_cleanup(sdp_state->remote_sdp); + sdp_state->remote_sdp = ao2_bump((struct ast_sdp *) remote_sdp); - switch (ast_stream_get_type(ast_stream_topology_get_stream(joint_capabilities->topology, i))) { - case AST_MEDIA_TYPE_AUDIO: - case AST_MEDIA_TYPE_VIDEO: - update_rtp_after_merge(sdp_state, state_stream->rtp, sdp_state->options, - remote_sdp, ast_sdp_get_m(remote_sdp, i)); - break; - case AST_MEDIA_TYPE_IMAGE: - update_udptl_after_merge(sdp_state, state_stream->udptl, sdp_state->options, - remote_sdp, ast_sdp_get_m(remote_sdp, i)); - break; - case AST_MEDIA_TYPE_UNKNOWN: - case AST_MEDIA_TYPE_TEXT: - case AST_MEDIA_TYPE_END: - break; - } - } + sdp_apply_negotiated_state(sdp_state); return 0; } @@ -1194,10 +2540,43 @@ const struct ast_sdp *ast_sdp_state_get_local_sdp(struct ast_sdp_state *sdp_stat { ast_assert(sdp_state != NULL); - if (sdp_state->role == SDP_ROLE_NOT_SET) { + switch (sdp_state->role) { + case SDP_ROLE_NOT_SET: ast_assert(sdp_state->local_sdp == NULL); sdp_state->role = SDP_ROLE_OFFERER; + + if (sdp_state->pending_topology_update) { + struct sdp_state_capabilities *capabilities; + + /* We have a topology update to perform before generating the offer */ + capabilities = merge_local_capabilities(sdp_state, + sdp_state->pending_topology_update); + if (!capabilities) { + break; + } + ast_stream_topology_free(sdp_state->pending_topology_update); + sdp_state->pending_topology_update = NULL; + set_proposed_capabilities(sdp_state, capabilities); + } + + /* + * Allow the system to configure the topology streams + * before we create the offer SDP. + */ + sdp_state_cb_offerer_config_topology(sdp_state, + sdp_state->proposed_capabilities->topology); + sdp_state->local_sdp = sdp_create_from_state(sdp_state, sdp_state->proposed_capabilities); + break; + case SDP_ROLE_OFFERER: + break; + case SDP_ROLE_ANSWERER: + if (!sdp_state->local_sdp + && sdp_state->negotiated_capabilities + && !sdp_state->remote_offer_rejected) { + sdp_state->local_sdp = sdp_create_from_state(sdp_state, sdp_state->negotiated_capabilities); + } + break; } return sdp_state->local_sdp; @@ -1237,35 +2616,63 @@ int ast_sdp_state_set_remote_sdp_from_impl(struct ast_sdp_state *sdp_state, cons return -1; } ret = ast_sdp_state_set_remote_sdp(sdp_state, sdp); - ast_sdp_free(sdp); + ao2_ref(sdp, -1); return ret; } -int ast_sdp_state_reset(struct ast_sdp_state *sdp_state) +int ast_sdp_state_is_offer_rejected(struct ast_sdp_state *sdp_state) +{ + return sdp_state->remote_offer_rejected; +} + +int ast_sdp_state_is_offerer(struct ast_sdp_state *sdp_state) +{ + return sdp_state->role == SDP_ROLE_OFFERER; +} + +int ast_sdp_state_is_answerer(struct ast_sdp_state *sdp_state) +{ + return sdp_state->role == SDP_ROLE_ANSWERER; +} + +int ast_sdp_state_restart_negotiations(struct ast_sdp_state *sdp_state) { ast_assert(sdp_state != NULL); - ast_sdp_free(sdp_state->local_sdp); + ao2_cleanup(sdp_state->local_sdp); sdp_state->local_sdp = NULL; - set_proposed_capabilities(sdp_state, NULL); - sdp_state->role = SDP_ROLE_NOT_SET; + sdp_state->remote_offer_rejected = 0; + + if (sdp_state->negotiated_capabilities) { + update_proposed_capabilities(sdp_state, sdp_state->negotiated_capabilities); + } return 0; } -int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *streams) +int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *topology) { - struct sdp_state_capabilities *capabilities; + struct ast_stream_topology *merged_topology; + ast_assert(sdp_state != NULL); - ast_assert(streams != NULL); + ast_assert(topology != NULL); - capabilities = merge_capabilities(sdp_state, streams, 1); - if (!capabilities) { - return -1; + if (sdp_state->pending_topology_update) { + merged_topology = merge_local_topologies(sdp_state, + sdp_state->pending_topology_update, topology, 0); + if (!merged_topology) { + return -1; + } + ast_stream_topology_free(sdp_state->pending_topology_update); + sdp_state->pending_topology_update = merged_topology; + } else { + sdp_state->pending_topology_update = ast_stream_topology_clone(topology); + if (!sdp_state->pending_topology_update) { + return -1; + } } - set_proposed_capabilities(sdp_state, capabilities); return 0; } @@ -1275,9 +2682,9 @@ void ast_sdp_state_set_local_address(struct ast_sdp_state *sdp_state, struct ast ast_assert(sdp_state != NULL); if (!address) { - ast_sockaddr_setnull(&sdp_state->proposed_capabilities->connection_address); + ast_sockaddr_setnull(&sdp_state->connection_address); } else { - ast_sockaddr_copy(&sdp_state->proposed_capabilities->connection_address, address); + ast_sockaddr_copy(&sdp_state->connection_address, address); } } @@ -1301,18 +2708,37 @@ int ast_sdp_state_set_connection_address(struct ast_sdp_state *sdp_state, int st return 0; } +void ast_sdp_state_set_global_locally_held(struct ast_sdp_state *sdp_state, unsigned int locally_held) +{ + ast_assert(sdp_state != NULL); + + sdp_state->locally_held = locally_held ? 1 : 0; +} + +unsigned int ast_sdp_state_get_global_locally_held(const struct ast_sdp_state *sdp_state) +{ + ast_assert(sdp_state != NULL); + + return sdp_state->locally_held; +} + void ast_sdp_state_set_locally_held(struct ast_sdp_state *sdp_state, int stream_index, unsigned int locally_held) { struct sdp_state_stream *stream_state; ast_assert(sdp_state != NULL); - stream_state = sdp_state_get_stream(sdp_state, stream_index); - if (!stream_state) { - return; + locally_held = locally_held ? 1 : 0; + + stream_state = sdp_state_get_joint_stream(sdp_state, stream_index); + if (stream_state) { + stream_state->locally_held = locally_held; } - stream_state->locally_held = locally_held; + stream_state = sdp_state_get_stream(sdp_state, stream_index); + if (stream_state) { + stream_state->locally_held = locally_held; + } } unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_state, @@ -1321,7 +2747,7 @@ unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_stat struct sdp_state_stream *stream_state; ast_assert(sdp_state != NULL); - stream_state = sdp_state_get_stream(sdp_state, stream_index); + stream_state = sdp_state_get_joint_stream(sdp_state, stream_index); if (!stream_state) { return 0; } @@ -1329,6 +2755,21 @@ unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_stat return stream_state->locally_held; } +unsigned int ast_sdp_state_get_remotely_held(const struct ast_sdp_state *sdp_state, + int stream_index) +{ + struct sdp_state_stream *stream_state; + + ast_assert(sdp_state != NULL); + + stream_state = sdp_state_get_joint_stream(sdp_state, stream_index); + if (!stream_state) { + return 0; + } + + return stream_state->remotely_held; +} + void ast_sdp_state_set_t38_parameters(struct ast_sdp_state *sdp_state, int stream_index, struct ast_control_t38_parameters *params) { @@ -1336,11 +2777,9 @@ void ast_sdp_state_set_t38_parameters(struct ast_sdp_state *sdp_state, ast_assert(sdp_state != NULL && params != NULL); stream_state = sdp_state_get_stream(sdp_state, stream_index); - if (!stream_state) { - return; + if (stream_state) { + stream_state->t38_local_params = *params; } - - stream_state->t38_local_params = *params; } /*! @@ -1374,8 +2813,161 @@ static void add_ssrc_attributes(struct ast_sdp_m_line *m_line, const struct ast_ ast_sdp_m_add_a(m_line, a_line); } +/*! + * \internal + * \brief Create a declined m-line from a remote requested stream. + * \since 15.0.0 + * + * \details + * Using the last received remote SDP create a declined stream + * m-line for the requested stream. The stream may be unsupported. + * + * \param sdp Our SDP under construction to append the declined stream. + * \param sdp_state + * \param stream_index Which remote SDP stream we are declining. + * + * \retval 0 on success. + * \retval -1 on failure. + */ +static int sdp_add_m_from_declined_remote_stream(struct ast_sdp *sdp, + const struct ast_sdp_state *sdp_state, int stream_index) +{ + const struct ast_sdp_m_line *m_line_remote; + struct ast_sdp_m_line *m_line; + int idx; + + ast_assert(sdp && sdp_state && sdp_state->remote_sdp); + ast_assert(stream_index < ast_sdp_get_m_count(sdp_state->remote_sdp)); + + /* + * The only way we can generate a declined unsupported stream + * m-line is if the remote offered it to us. + */ + m_line_remote = ast_sdp_get_m(sdp_state->remote_sdp, stream_index); + + /* Copy remote SDP stream m-line except for port number. */ + m_line = ast_sdp_m_alloc(m_line_remote->type, 0, m_line_remote->port_count, + m_line_remote->proto, NULL); + if (!m_line) { + return -1; + } + + /* Copy any m-line payload strings from the remote SDP */ + for (idx = 0; idx < ast_sdp_m_get_payload_count(m_line_remote); ++idx) { + const struct ast_sdp_payload *payload_remote; + struct ast_sdp_payload *payload; + + payload_remote = ast_sdp_m_get_payload(m_line_remote, idx); + payload = ast_sdp_payload_alloc(payload_remote->fmt); + if (!payload) { + ast_sdp_m_free(m_line); + return -1; + } + if (ast_sdp_m_add_payload(m_line, payload)) { + ast_sdp_payload_free(payload); + ast_sdp_m_free(m_line); + return -1; + } + } + + if (ast_sdp_add_m(sdp, m_line)) { + ast_sdp_m_free(m_line); + return -1; + } + + return 0; +} + +/*! + * \internal + * \brief Create a declined m-line for our SDP stream. + * \since 15.0.0 + * + * \param sdp Our SDP under construction to append the declined stream. + * \param sdp_state + * \param type Stream type we are declining. + * \param stream_index Which remote SDP stream we are declining. + * + * \retval 0 on success. + * \retval -1 on failure. + */ +static int sdp_add_m_from_declined_stream(struct ast_sdp *sdp, + const struct ast_sdp_state *sdp_state, enum ast_media_type type, int stream_index) +{ + struct ast_sdp_m_line *m_line; + const char *proto; + const char *fmt; + struct ast_sdp_payload *payload; + + if (sdp_state->role == SDP_ROLE_ANSWERER) { + /* We are declining the remote stream or it is still declined. */ + return sdp_add_m_from_declined_remote_stream(sdp, sdp_state, stream_index); + } + + /* Send declined remote stream in our offer if the type matches. */ + if (sdp_state->remote_sdp + && stream_index < ast_sdp_get_m_count(sdp_state->remote_sdp)) { + if (!sdp_is_stream_type_supported(type) + || !strcasecmp(ast_sdp_get_m(sdp_state->remote_sdp, stream_index)->type, + ast_codec_media_type2str(type))) { + /* Stream is still declined */ + return sdp_add_m_from_declined_remote_stream(sdp, sdp_state, stream_index); + } + } + + /* Build a new declined stream in our offer. */ + switch (type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + proto = "RTP/AVP"; + break; + case AST_MEDIA_TYPE_IMAGE: + proto = "udptl"; + break; + default: + /* Stream type not supported */ + ast_assert(0); + return -1; + } + m_line = ast_sdp_m_alloc(ast_codec_media_type2str(type), 0, 1, proto, NULL); + if (!m_line) { + return -1; + } + + /* Add a dummy static payload type */ + switch (type) { + case AST_MEDIA_TYPE_AUDIO: + fmt = "0"; /* ulaw */ + break; + case AST_MEDIA_TYPE_VIDEO: + fmt = "31"; /* H.261 */ + break; + case AST_MEDIA_TYPE_IMAGE: + fmt = "t38"; /* T.38 */ + break; + default: + /* Stream type not supported */ + ast_assert(0); + ast_sdp_m_free(m_line); + return -1; + } + payload = ast_sdp_payload_alloc(fmt); + if (!payload || ast_sdp_m_add_payload(m_line, payload)) { + ast_sdp_payload_free(payload); + ast_sdp_m_free(m_line); + return -1; + } + + if (ast_sdp_add_m(sdp, m_line)) { + ast_sdp_m_free(m_line); + return -1; + } + + return 0; +} + static int sdp_add_m_from_rtp_stream(struct ast_sdp *sdp, const struct ast_sdp_state *sdp_state, - const struct ast_sdp_options *options, const struct sdp_state_capabilities *capabilities, int stream_index) + const struct sdp_state_capabilities *capabilities, int stream_index) { struct ast_stream *stream; struct ast_sdp_m_line *m_line; @@ -1390,15 +2982,19 @@ static int sdp_add_m_from_rtp_stream(struct ast_sdp *sdp, const struct ast_sdp_s struct sdp_state_stream *stream_state; struct ast_rtp_instance *rtp; struct ast_sdp_a_line *a_line; + const struct ast_sdp_options *options; + const char *direction; stream = ast_stream_topology_get_stream(capabilities->topology, stream_index); - ast_assert(sdp && options && stream); + ast_assert(sdp && sdp_state && stream); + options = sdp_state->options; caps = ast_stream_get_formats(stream); stream_state = AST_VECTOR_GET(&capabilities->streams, stream_index); - if (stream_state->rtp && caps && ast_format_cap_count(caps)) { + if (stream_state->rtp && caps && ast_format_cap_count(caps) + && AST_STREAM_STATE_REMOVED != ast_stream_get_state(stream)) { rtp = stream_state->rtp->instance; } else { /* This is a disabled stream */ @@ -1408,7 +3004,7 @@ static int sdp_add_m_from_rtp_stream(struct ast_sdp *sdp, const struct ast_sdp_s if (rtp) { struct ast_sockaddr address_rtp; - if (ast_sdp_state_get_stream_connection_address(sdp_state, 0, &address_rtp)) { + if (sdp_state_stream_get_connection_address(sdp_state, stream_state, &address_rtp)) { return -1; } rtp_port = ast_sockaddr_port(&address_rtp); @@ -1416,129 +3012,119 @@ static int sdp_add_m_from_rtp_stream(struct ast_sdp *sdp, const struct ast_sdp_s rtp_port = 0; } - m_line = ast_sdp_m_alloc( - ast_codec_media_type2str(ast_stream_get_type(stream)), - rtp_port, 1, + media_type = ast_stream_get_type(stream); + if (!rtp_port) { + /* Declined/disabled stream */ + return sdp_add_m_from_declined_stream(sdp, sdp_state, media_type, stream_index); + } + + /* Stream is not declined/disabled */ + m_line = ast_sdp_m_alloc(ast_codec_media_type2str(media_type), rtp_port, 1, options->encryption != AST_SDP_ENCRYPTION_DISABLED ? "RTP/SAVP" : "RTP/AVP", NULL); if (!m_line) { return -1; } - if (rtp_port) { - /* Stream is not declined/disabled */ - for (i = 0; i < ast_format_cap_count(caps); i++) { - struct ast_format *format = ast_format_cap_get_format(caps, i); - - rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(rtp), 1, - format, 0); - if (rtp_code == -1) { - ast_log(LOG_WARNING,"Unable to get rtp codec payload code for %s\n", - ast_format_get_name(format)); - ao2_ref(format, -1); - continue; - } - - if (ast_sdp_m_add_format(m_line, options, rtp_code, 1, format, 0)) { - ast_sdp_m_free(m_line); - ao2_ref(format, -1); - return -1; - } - - if (ast_format_get_maximum_ms(format) - && ((ast_format_get_maximum_ms(format) < max_packet_size) - || !max_packet_size)) { - max_packet_size = ast_format_get_maximum_ms(format); - } + for (i = 0; i < ast_format_cap_count(caps); i++) { + struct ast_format *format = ast_format_cap_get_format(caps, i); + rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(rtp), 1, + format, 0); + if (rtp_code == -1) { + ast_log(LOG_WARNING,"Unable to get rtp codec payload code for %s\n", + ast_format_get_name(format)); ao2_ref(format, -1); + continue; } - media_type = ast_stream_get_type(stream); - if (media_type != AST_MEDIA_TYPE_VIDEO - && (options->dtmf == AST_SDP_DTMF_RFC_4733 || options->dtmf == AST_SDP_DTMF_AUTO)) { - i = AST_RTP_DTMF; - rtp_code = ast_rtp_codecs_payload_code( - ast_rtp_instance_get_codecs(rtp), 0, NULL, i); - if (-1 < rtp_code) { - if (ast_sdp_m_add_format(m_line, options, rtp_code, 0, NULL, i)) { - ast_sdp_m_free(m_line); - return -1; - } - - snprintf(tmp, sizeof(tmp), "%d 0-16", rtp_code); - a_line = ast_sdp_a_alloc("fmtp", tmp); - if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { - ast_sdp_a_free(a_line); - ast_sdp_m_free(m_line); - return -1; - } - } + if (ast_sdp_m_add_format(m_line, options, rtp_code, 1, format, 0)) { + ast_sdp_m_free(m_line); + ao2_ref(format, -1); + return -1; } - /* If ptime is set add it as an attribute */ - min_packet_size = ast_rtp_codecs_get_framing(ast_rtp_instance_get_codecs(rtp)); - if (!min_packet_size) { - min_packet_size = ast_format_cap_get_framing(caps); + if (ast_format_get_maximum_ms(format) + && ((ast_format_get_maximum_ms(format) < max_packet_size) + || !max_packet_size)) { + max_packet_size = ast_format_get_maximum_ms(format); } - if (min_packet_size) { - snprintf(tmp, sizeof(tmp), "%d", min_packet_size); - a_line = ast_sdp_a_alloc("ptime", tmp); - if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { - ast_sdp_a_free(a_line); + ao2_ref(format, -1); + } + + if (media_type != AST_MEDIA_TYPE_VIDEO + && (options->dtmf == AST_SDP_DTMF_RFC_4733 || options->dtmf == AST_SDP_DTMF_AUTO)) { + i = AST_RTP_DTMF; + rtp_code = ast_rtp_codecs_payload_code( + ast_rtp_instance_get_codecs(rtp), 0, NULL, i); + if (-1 < rtp_code) { + if (ast_sdp_m_add_format(m_line, options, rtp_code, 0, NULL, i)) { ast_sdp_m_free(m_line); return -1; } - } - if (max_packet_size) { - snprintf(tmp, sizeof(tmp), "%d", max_packet_size); - a_line = ast_sdp_a_alloc("maxptime", tmp); + snprintf(tmp, sizeof(tmp), "%d 0-16", rtp_code); + a_line = ast_sdp_a_alloc("fmtp", tmp); if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { ast_sdp_a_free(a_line); ast_sdp_m_free(m_line); return -1; } } + } - a_line = ast_sdp_a_alloc(ast_sdp_state_get_locally_held(sdp_state, stream_index) - ? "sendonly" : "sendrecv", ""); + /* If ptime is set add it as an attribute */ + min_packet_size = ast_rtp_codecs_get_framing(ast_rtp_instance_get_codecs(rtp)); + if (!min_packet_size) { + min_packet_size = ast_format_cap_get_framing(caps); + } + if (min_packet_size) { + snprintf(tmp, sizeof(tmp), "%d", min_packet_size); + + a_line = ast_sdp_a_alloc("ptime", tmp); if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { ast_sdp_a_free(a_line); ast_sdp_m_free(m_line); return -1; } + } - add_ssrc_attributes(m_line, options, rtp); - } else { - /* Declined/disabled stream */ - struct ast_sdp_payload *payload; - const char *fmt; + if (max_packet_size) { + snprintf(tmp, sizeof(tmp), "%d", max_packet_size); + a_line = ast_sdp_a_alloc("maxptime", tmp); + if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { + ast_sdp_a_free(a_line); + ast_sdp_m_free(m_line); + return -1; + } + } - /* - * Add a static payload type placeholder to the declined/disabled stream. - * - * XXX We should use the default payload type in the received offer but - * we don't have that available. - */ - switch (ast_stream_get_type(stream)) { - default: - case AST_MEDIA_TYPE_AUDIO: - fmt = "0"; /* ulaw */ - break; - case AST_MEDIA_TYPE_VIDEO: - fmt = "31"; /* H.261 */ - break; + if (sdp_state->locally_held || stream_state->locally_held) { + if (stream_state->remotely_held) { + direction = "inactive"; + } else { + direction = "sendonly"; } - payload = ast_sdp_payload_alloc(fmt); - if (!payload || ast_sdp_m_add_payload(m_line, payload)) { - ast_sdp_payload_free(payload); + } else { + if (stream_state->remotely_held) { + direction = "recvonly"; + } else { + /* Default is "sendrecv" */ + direction = NULL; + } + } + if (direction) { + a_line = ast_sdp_a_alloc(direction, ""); + if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { + ast_sdp_a_free(a_line); ast_sdp_m_free(m_line); return -1; } } + add_ssrc_attributes(m_line, options, rtp); + if (ast_sdp_add_m(sdp, m_line)) { ast_sdp_m_free(m_line); return -1; @@ -1569,11 +3155,12 @@ static unsigned int t38_get_rate(enum ast_control_t38_rate rate) } static int sdp_add_m_from_udptl_stream(struct ast_sdp *sdp, const struct ast_sdp_state *sdp_state, - const struct ast_sdp_options *options, const struct sdp_state_capabilities *capabilities, int stream_index) + const struct sdp_state_capabilities *capabilities, int stream_index) { struct ast_stream *stream; struct ast_sdp_m_line *m_line; struct ast_sdp_payload *payload; + enum ast_media_type media_type; char tmp[64]; struct sdp_state_udptl *udptl; struct ast_sdp_a_line *a_line; @@ -1582,10 +3169,11 @@ static int sdp_add_m_from_udptl_stream(struct ast_sdp *sdp, const struct ast_sdp stream = ast_stream_topology_get_stream(capabilities->topology, stream_index); - ast_assert(sdp && options && stream); + ast_assert(sdp && sdp_state && stream); stream_state = AST_VECTOR_GET(&capabilities->streams, stream_index); - if (stream_state->udptl) { + if (stream_state->udptl + && AST_STREAM_STATE_REMOVED != ast_stream_get_state(stream)) { udptl = stream_state->udptl; } else { /* This is a disabled stream */ @@ -1595,7 +3183,7 @@ static int sdp_add_m_from_udptl_stream(struct ast_sdp *sdp, const struct ast_sdp if (udptl) { struct ast_sockaddr address_udptl; - if (ast_sdp_state_get_stream_connection_address(sdp_state, 0, &address_udptl)) { + if (sdp_state_stream_get_connection_address(sdp_state, stream_state, &address_udptl)) { return -1; } udptl_port = ast_sockaddr_port(&address_udptl); @@ -1603,9 +3191,15 @@ static int sdp_add_m_from_udptl_stream(struct ast_sdp *sdp, const struct ast_sdp udptl_port = 0; } - m_line = ast_sdp_m_alloc( - ast_codec_media_type2str(ast_stream_get_type(stream)), - udptl_port, 1, "udptl", NULL); + media_type = ast_stream_get_type(stream); + if (!udptl_port) { + /* Declined/disabled stream */ + return sdp_add_m_from_declined_stream(sdp, sdp_state, media_type, stream_index); + } + + /* Stream is not declined/disabled */ + m_line = ast_sdp_m_alloc(ast_codec_media_type2str(media_type), udptl_port, 1, + "udptl", NULL); if (!m_line) { return -1; } @@ -1617,100 +3211,95 @@ static int sdp_add_m_from_udptl_stream(struct ast_sdp *sdp, const struct ast_sdp return -1; } - if (udptl_port) { - /* Stream is not declined/disabled */ - stream_state = sdp_state_get_stream(sdp_state, stream_index); + snprintf(tmp, sizeof(tmp), "%u", stream_state->t38_local_params.version); + a_line = ast_sdp_a_alloc("T38FaxVersion", tmp); + if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { + ast_sdp_a_free(a_line); + ast_sdp_m_free(m_line); + return -1; + } + + snprintf(tmp, sizeof(tmp), "%u", t38_get_rate(stream_state->t38_local_params.rate)); + a_line = ast_sdp_a_alloc("T38FaxMaxBitRate", tmp); + if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { + ast_sdp_a_free(a_line); + ast_sdp_m_free(m_line); + return -1; + } - snprintf(tmp, sizeof(tmp), "%u", stream_state->t38_local_params.version); - a_line = ast_sdp_a_alloc("T38FaxVersion", tmp); + if (stream_state->t38_local_params.fill_bit_removal) { + a_line = ast_sdp_a_alloc("T38FaxFillBitRemoval", ""); if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { ast_sdp_a_free(a_line); ast_sdp_m_free(m_line); return -1; } + } - snprintf(tmp, sizeof(tmp), "%u", t38_get_rate(stream_state->t38_local_params.rate)); - a_line = ast_sdp_a_alloc("T38FaxMaxBitRate", tmp); + if (stream_state->t38_local_params.transcoding_mmr) { + a_line = ast_sdp_a_alloc("T38FaxTranscodingMMR", ""); if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { ast_sdp_a_free(a_line); ast_sdp_m_free(m_line); return -1; } + } - if (stream_state->t38_local_params.fill_bit_removal) { - a_line = ast_sdp_a_alloc("T38FaxFillBitRemoval", ""); - if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { - ast_sdp_a_free(a_line); - ast_sdp_m_free(m_line); - return -1; - } + if (stream_state->t38_local_params.transcoding_jbig) { + a_line = ast_sdp_a_alloc("T38FaxTranscodingJBIG", ""); + if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { + ast_sdp_a_free(a_line); + ast_sdp_m_free(m_line); + return -1; } + } - if (stream_state->t38_local_params.transcoding_mmr) { - a_line = ast_sdp_a_alloc("T38FaxTranscodingMMR", ""); - if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { - ast_sdp_a_free(a_line); - ast_sdp_m_free(m_line); - return -1; - } + switch (stream_state->t38_local_params.rate_management) { + case AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF: + a_line = ast_sdp_a_alloc("T38FaxRateManagement", "transferredTCF"); + if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { + ast_sdp_a_free(a_line); + ast_sdp_m_free(m_line); + return -1; } - - if (stream_state->t38_local_params.transcoding_jbig) { - a_line = ast_sdp_a_alloc("T38FaxTranscodingJBIG", ""); - if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { - ast_sdp_a_free(a_line); - ast_sdp_m_free(m_line); - return -1; - } + break; + case AST_T38_RATE_MANAGEMENT_LOCAL_TCF: + a_line = ast_sdp_a_alloc("T38FaxRateManagement", "localTCF"); + if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { + ast_sdp_a_free(a_line); + ast_sdp_m_free(m_line); + return -1; } + break; + } - switch (stream_state->t38_local_params.rate_management) { - case AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF: - a_line = ast_sdp_a_alloc("T38FaxRateManagement", "transferredTCF"); - if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { - ast_sdp_a_free(a_line); - ast_sdp_m_free(m_line); - return -1; - } - break; - case AST_T38_RATE_MANAGEMENT_LOCAL_TCF: - a_line = ast_sdp_a_alloc("T38FaxRateManagement", "localTCF"); - if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { - ast_sdp_a_free(a_line); - ast_sdp_m_free(m_line); - return -1; - } - break; - } + snprintf(tmp, sizeof(tmp), "%u", ast_udptl_get_local_max_datagram(udptl->instance)); + a_line = ast_sdp_a_alloc("T38FaxMaxDatagram", tmp); + if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { + ast_sdp_a_free(a_line); + ast_sdp_m_free(m_line); + return -1; + } - snprintf(tmp, sizeof(tmp), "%u", ast_udptl_get_local_max_datagram(udptl->instance)); - a_line = ast_sdp_a_alloc("T38FaxMaxDatagram", tmp); + switch (ast_udptl_get_error_correction_scheme(udptl->instance)) { + case UDPTL_ERROR_CORRECTION_NONE: + break; + case UDPTL_ERROR_CORRECTION_FEC: + a_line = ast_sdp_a_alloc("T38FaxUdpEC", "t38UDPFEC"); if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { ast_sdp_a_free(a_line); ast_sdp_m_free(m_line); return -1; } - - switch (ast_udptl_get_error_correction_scheme(udptl->instance)) { - case UDPTL_ERROR_CORRECTION_NONE: - break; - case UDPTL_ERROR_CORRECTION_FEC: - a_line = ast_sdp_a_alloc("T38FaxUdpEC", "t38UDPFEC"); - if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { - ast_sdp_a_free(a_line); - ast_sdp_m_free(m_line); - return -1; - } - break; - case UDPTL_ERROR_CORRECTION_REDUNDANCY: - a_line = ast_sdp_a_alloc("T38FaxUdpEC", "t38UDPRedundancy"); - if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { - ast_sdp_a_free(a_line); - ast_sdp_m_free(m_line); - return -1; - } - break; + break; + case UDPTL_ERROR_CORRECTION_REDUNDANCY: + a_line = ast_sdp_a_alloc("T38FaxUdpEC", "t38UDPRedundancy"); + if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { + ast_sdp_a_free(a_line); + ast_sdp_m_free(m_line); + return -1; } + break; } if (ast_sdp_add_m(sdp, m_line)) { @@ -1744,7 +3333,7 @@ static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_sta uint32_t t; int stream_count; - options = ast_sdp_state_get_options(sdp_state); + options = sdp_state->options; topology = capabilities->topology; t = tv.tv_sec + 2208988800UL; @@ -1773,23 +3362,26 @@ static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_sta } stream_count = ast_stream_topology_get_count(topology); - for (stream_num = 0; stream_num < stream_count; stream_num++) { switch (ast_stream_get_type(ast_stream_topology_get_stream(topology, stream_num))) { case AST_MEDIA_TYPE_AUDIO: case AST_MEDIA_TYPE_VIDEO: - if (sdp_add_m_from_rtp_stream(sdp, sdp_state, options, capabilities, stream_num)) { + if (sdp_add_m_from_rtp_stream(sdp, sdp_state, capabilities, stream_num)) { goto error; } break; case AST_MEDIA_TYPE_IMAGE: - if (sdp_add_m_from_udptl_stream(sdp, sdp_state, options, capabilities, stream_num)) { + if (sdp_add_m_from_udptl_stream(sdp, sdp_state, capabilities, stream_num)) { goto error; } break; case AST_MEDIA_TYPE_UNKNOWN: case AST_MEDIA_TYPE_TEXT: case AST_MEDIA_TYPE_END: + /* Decline any of these streams from the remote. */ + if (sdp_add_m_from_declined_remote_stream(sdp, sdp_state, stream_num)) { + goto error; + } break; } } @@ -1798,7 +3390,7 @@ static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_sta error: if (sdp) { - ast_sdp_free(sdp); + ao2_ref(sdp, -1); } else { ast_sdp_t_free(t_line); ast_sdp_s_free(s_line); diff --git a/main/stream.c b/main/stream.c index 093cd5450..89ed0dc53 100644 --- a/main/stream.c +++ b/main/stream.c @@ -214,6 +214,23 @@ const char *ast_stream_state2str(enum ast_stream_state state) } } +enum ast_stream_state ast_stream_str2state(const char *str) +{ + if (!strcmp("sendrecv", str)) { + return AST_STREAM_STATE_SENDRECV; + } + if (!strcmp("sendonly", str)) { + return AST_STREAM_STATE_SENDONLY; + } + if (!strcmp("recvonly", str)) { + return AST_STREAM_STATE_RECVONLY; + } + if (!strcmp("inactive", str)) { + return AST_STREAM_STATE_INACTIVE; + } + return AST_STREAM_STATE_REMOVED; +} + void *ast_stream_get_data(struct ast_stream *stream, enum ast_stream_data_slot slot) { ast_assert(stream != NULL); diff --git a/res/res_agi.c b/res/res_agi.c index e8497f7ca..466063557 100644 --- a/res/res_agi.c +++ b/res/res_agi.c @@ -2393,7 +2393,7 @@ static int handle_waitfordigit(struct ast_channel *chan, AGI *agi, int argc, con return RESULT_SHOWUSAGE; if (sscanf(argv[3], "%30d", &to) != 1) return RESULT_SHOWUSAGE; - res = ast_waitfordigit_full(chan, to, agi->audio, agi->ctrl); + res = ast_waitfordigit_full(chan, to, NULL, agi->audio, agi->ctrl); ast_agi_send(agi->fd, chan, "200 result=%d\n", res); return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; } @@ -2673,7 +2673,7 @@ static int handle_getoption(struct ast_channel *chan, AGI *agi, int argc, const /* If the user didnt press a key, wait for digitTimeout*/ if (res == 0 ) { - res = ast_waitfordigit_full(chan, timeout, agi->audio, agi->ctrl); + res = ast_waitfordigit_full(chan, timeout, NULL, agi->audio, agi->ctrl); /* Make sure the new result is in the escape digits of the GET OPTION */ if ( !strchr(edigits,res) ) res=0; diff --git a/res/res_sdp_translator_pjmedia.c b/res/res_sdp_translator_pjmedia.c index 85f246e83..d80f3d554 100644 --- a/res/res_sdp_translator_pjmedia.c +++ b/res/res_sdp_translator_pjmedia.c @@ -484,7 +484,7 @@ AST_TEST_DEFINE(pjmedia_to_sdp_test) } cleanup: - ast_sdp_free(sdp); + ao2_cleanup(sdp); ast_sdp_translator_free(translator); pj_pool_release(pool); return res; @@ -560,7 +560,7 @@ AST_TEST_DEFINE(sdp_to_pjmedia_test) } cleanup: - ast_sdp_free(sdp); + ao2_cleanup(sdp); ast_sdp_translator_free(translator); pj_pool_release(pool); return res; diff --git a/res/res_stasis_device_state.c b/res/res_stasis_device_state.c index 344cb40c9..276a98b93 100644 --- a/res/res_stasis_device_state.c +++ b/res/res_stasis_device_state.c @@ -106,7 +106,6 @@ static int device_state_subscriptions_cmp(void *obj, void *arg, int flags) static void device_state_subscription_destroy(void *obj) { struct device_state_subscription *sub = obj; - sub->sub = stasis_unsubscribe_and_join(sub->sub); ast_string_field_free_memory(sub); } @@ -152,6 +151,9 @@ static struct device_state_subscription *find_device_state_subscription( static void remove_device_state_subscription( struct device_state_subscription *sub) { + if (sub->sub) { + sub->sub = stasis_unsubscribe_and_join(sub->sub); + } ao2_unlink_flags(device_state_subscriptions, sub, OBJ_NOLOCK); } diff --git a/rest-api-templates/make_ari_stubs.py b/rest-api-templates/make_ari_stubs.py index 4e02cdd5f..0aba06d6d 100755 --- a/rest-api-templates/make_ari_stubs.py +++ b/rest-api-templates/make_ari_stubs.py @@ -41,7 +41,7 @@ def rel(file): """ return os.path.join(TOPDIR, file) -WIKI_PREFIX = 'Asterisk 13' +WIKI_PREFIX = 'Asterisk 16' API_TRANSFORMS = [ Transform(rel('api.wiki.mustache'), diff --git a/rest-api/resources.json b/rest-api/resources.json index df5b8d6fd..920bb896d 100644 --- a/rest-api/resources.json +++ b/rest-api/resources.json @@ -2,7 +2,7 @@ "_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.", "_author": "David M. Lee, II <dlee@digium.com>", "_svn_revision": "$Revision$", - "apiVersion": "2.0.0", + "apiVersion": "3.0.0", "swaggerVersion": "1.1", "basePath": "http://localhost:8088/ari", "apis": [ diff --git a/sounds/Makefile b/sounds/Makefile index 9dfd0c61c..381776f72 100644 --- a/sounds/Makefile +++ b/sounds/Makefile @@ -20,7 +20,7 @@ SOUNDS_DIR:=$(DESTDIR)$(ASTDATADIR)/sounds SOUNDS_CACHE_DIR?= MOH_DIR:=$(DESTDIR)$(ASTDATADIR)/moh CORE_SOUNDS_VERSION:=1.6 -EXTRA_SOUNDS_VERSION:=1.5 +EXTRA_SOUNDS_VERSION:=1.5.1 MOH_VERSION:=2.03 SOUNDS_URL:=http://downloads.asterisk.org/pub/telephony/sounds/releases MCS:=$(subst -EN-,-en-,$(MENUSELECT_CORE_SOUNDS)) diff --git a/tests/test_config.c b/tests/test_config.c index 6635c6f78..d73710860 100644 --- a/tests/test_config.c +++ b/tests/test_config.c @@ -41,6 +41,7 @@ #include "asterisk/config_options.h" #include "asterisk/netsock2.h" #include "asterisk/acl.h" +#include "asterisk/app.h" #include "asterisk/pbx.h" #include "asterisk/frame.h" #include "asterisk/utils.h" @@ -1080,6 +1081,13 @@ enum { ast_test_status_update(test, "ast_parse_arg double failed with %f != %f\n", *r, e); \ ret = AST_TEST_FAIL; \ } \ + } else if (((flags) & PARSE_TYPE) == PARSE_TIMELEN) { \ + int *r = (int *) (void *) result; \ + int e = (int) expected_result; \ + if (*r != e) { \ + ast_test_status_update(test, "ast_parse_arg timelen failed with %d != %d\n", *r, e); \ + ret = AST_TEST_FAIL; \ + } \ } \ } \ *(result) = DEFAULTVAL; \ @@ -1090,6 +1098,7 @@ AST_TEST_DEFINE(ast_parse_arg_test) int ret = AST_TEST_PASS; int32_t int32_t_val = DEFAULTVAL; uint32_t uint32_t_val = DEFAULTVAL; + int timelen_val = DEFAULTVAL; double double_val = DEFAULTVAL; switch (cmd) { @@ -1222,6 +1231,60 @@ AST_TEST_DEFINE(ast_parse_arg_test) TEST_PARSE(" -123", EXPECT_FAIL, DEFAULTVAL, PARSE_UINT32, &uint32_t_val); + /* timelen testing */ + TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS); + TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS); + TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS); + TEST_PARSE("not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS); + TEST_PARSE("7not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS); + + TEST_PARSE("123s", EXPECT_SUCCEED, 123000, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS); + TEST_PARSE("-123s", EXPECT_SUCCEED, -123000, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS); + TEST_PARSE("1m", EXPECT_SUCCEED, 60000, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS); + TEST_PARSE("1", EXPECT_SUCCEED, 60000, PARSE_TIMELEN, &timelen_val, TIMELEN_MINUTES); + TEST_PARSE("1h", EXPECT_SUCCEED, 3600000, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS); + TEST_PARSE("1", EXPECT_SUCCEED, 3600000, PARSE_TIMELEN, &timelen_val, TIMELEN_HOURS); + + TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN | PARSE_DEFAULT, &timelen_val, TIMELEN_MILLISECONDS, 7); + TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN | PARSE_DEFAULT, &timelen_val, TIMELEN_MILLISECONDS, 7); + TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN | PARSE_DEFAULT, &timelen_val, TIMELEN_MILLISECONDS, 7); + TEST_PARSE("not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT, &timelen_val, TIMELEN_MILLISECONDS, 7); + TEST_PARSE("7not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT, &timelen_val, TIMELEN_MILLISECONDS, 7); + + TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 0, 200); + TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -200, 100); + TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -1, 0); + TEST_PARSE("123", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 0, 122); + TEST_PARSE("-123", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -122, 100); + TEST_PARSE("0", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 1, 100); + TEST_PARSE("not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, INT_MIN, INT_MAX); + TEST_PARSE("7not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, INT_MIN, INT_MAX); + TEST_PARSE("123", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 0, 200); + TEST_PARSE("-123", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -200, 100); + TEST_PARSE("0", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -1, 0); + TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 0, 122); + TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -122, 100); + TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 1, 100); + TEST_PARSE("not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, INT_MIN, INT_MAX); + TEST_PARSE("7not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, INT_MIN, INT_MAX); + + TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 0, 200); + TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -200, 100); + TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -1, 0); + TEST_PARSE("123", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 0, 122); + TEST_PARSE("-123", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -122, 100); + TEST_PARSE("0", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 1, 100); + TEST_PARSE("not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, INT_MIN, INT_MAX); + TEST_PARSE("7not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, INT_MIN, INT_MAX); + TEST_PARSE("123", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 0, 200); + TEST_PARSE("-123", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -200, 100); + TEST_PARSE("0", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -1, 0); + TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 0, 122); + TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -122, 100); + TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 1, 100); + TEST_PARSE("not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, INT_MIN, INT_MAX); + TEST_PARSE("7not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, INT_MIN, INT_MAX); + /* double testing */ TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_DOUBLE, &double_val); TEST_PARSE("123.123", EXPECT_SUCCEED, 123.123, PARSE_DOUBLE, &double_val); @@ -1281,6 +1344,10 @@ struct test_item { ); int32_t intopt; uint32_t uintopt; + int timelenopt1; + int timelenopt2; + int timelenopt3; + int timelenopt4; unsigned int flags; double doubleopt; struct ast_sockaddr sockaddropt; @@ -1435,6 +1502,8 @@ AST_TEST_DEFINE(config_options_test) #define INT_CONFIG "-1" #define UINT_DEFAULT "2" #define UINT_CONFIG "1" +#define TIMELEN_DEFAULT "2" +#define TIMELEN_CONFIG "1" #define DOUBLE_DEFAULT "1.1" #define DOUBLE_CONFIG "0.1" #define SOCKADDR_DEFAULT "4.3.2.1:4321" @@ -1469,6 +1538,10 @@ AST_TEST_DEFINE(config_options_test) /* Register all options */ aco_option_register(&cfg_info, "intopt", ACO_EXACT, config_test_conf.types, INT_DEFAULT, OPT_INT_T, 0, FLDSET(struct test_item, intopt)); aco_option_register(&cfg_info, "uintopt", ACO_EXACT, config_test_conf.types, UINT_DEFAULT, OPT_UINT_T, 0, FLDSET(struct test_item, uintopt)); + aco_option_register(&cfg_info, "timelenopt1", ACO_EXACT, config_test_conf.types, TIMELEN_DEFAULT, OPT_TIMELEN_T, 0, FLDSET(struct test_item, timelenopt1), TIMELEN_MILLISECONDS); + aco_option_register(&cfg_info, "timelenopt2", ACO_EXACT, config_test_conf.types, TIMELEN_DEFAULT, OPT_TIMELEN_T, 0, FLDSET(struct test_item, timelenopt2), TIMELEN_SECONDS); + aco_option_register(&cfg_info, "timelenopt3", ACO_EXACT, config_test_conf.types, TIMELEN_DEFAULT, OPT_TIMELEN_T, 0, FLDSET(struct test_item, timelenopt3), TIMELEN_MINUTES); + aco_option_register(&cfg_info, "timelenopt4", ACO_EXACT, config_test_conf.types, TIMELEN_DEFAULT, OPT_TIMELEN_T, 0, FLDSET(struct test_item, timelenopt4), TIMELEN_HOURS); aco_option_register(&cfg_info, "doubleopt", ACO_EXACT, config_test_conf.types, DOUBLE_DEFAULT, OPT_DOUBLE_T, 0, FLDSET(struct test_item, doubleopt)); aco_option_register(&cfg_info, "sockaddropt", ACO_EXACT, config_test_conf.types, SOCKADDR_DEFAULT, OPT_SOCKADDR_T, 0, FLDSET(struct test_item, sockaddropt)); aco_option_register(&cfg_info, "boolopt", ACO_EXACT, config_test_conf.types, BOOL_DEFAULT, OPT_BOOL_T, 1, FLDSET(struct test_item, boolopt)); @@ -1490,6 +1563,14 @@ AST_TEST_DEFINE(config_options_test) ast_parse_arg(INT_DEFAULT, PARSE_INT32, &defaults.intopt); ast_parse_arg(INT_CONFIG, PARSE_INT32, &configs.intopt); + ast_parse_arg(TIMELEN_DEFAULT, PARSE_TIMELEN, &defaults.timelenopt1, TIMELEN_MILLISECONDS); + ast_parse_arg(TIMELEN_CONFIG, PARSE_TIMELEN, &configs.timelenopt1, TIMELEN_MILLISECONDS); + ast_parse_arg(TIMELEN_DEFAULT, PARSE_TIMELEN, &defaults.timelenopt2, TIMELEN_SECONDS); + ast_parse_arg(TIMELEN_CONFIG, PARSE_TIMELEN, &configs.timelenopt2, TIMELEN_SECONDS); + ast_parse_arg(TIMELEN_DEFAULT, PARSE_TIMELEN, &defaults.timelenopt3, TIMELEN_MINUTES); + ast_parse_arg(TIMELEN_CONFIG, PARSE_TIMELEN, &configs.timelenopt3, TIMELEN_MINUTES); + ast_parse_arg(TIMELEN_DEFAULT, PARSE_TIMELEN, &defaults.timelenopt4, TIMELEN_HOURS); + ast_parse_arg(TIMELEN_CONFIG, PARSE_TIMELEN, &configs.timelenopt4, TIMELEN_HOURS); ast_parse_arg(UINT_DEFAULT, PARSE_UINT32, &defaults.uintopt); ast_parse_arg(UINT_CONFIG, PARSE_UINT32, &configs.uintopt); ast_parse_arg(DOUBLE_DEFAULT, PARSE_DOUBLE, &defaults.doubleopt); @@ -1551,6 +1632,10 @@ AST_TEST_DEFINE(config_options_test) NOT_EQUAL_FAIL(intopt, "%d"); NOT_EQUAL_FAIL(uintopt, "%u"); + NOT_EQUAL_FAIL(timelenopt1, "%d"); + NOT_EQUAL_FAIL(timelenopt2, "%d"); + NOT_EQUAL_FAIL(timelenopt3, "%d"); + NOT_EQUAL_FAIL(timelenopt4, "%d"); NOT_EQUAL_FAIL(boolopt, "%d"); NOT_EQUAL_FAIL(flags, "%u"); NOT_EQUAL_FAIL(customopt, "%d"); diff --git a/tests/test_sdp.c b/tests/test_sdp.c index 7eef3f741..0ab8ec8ae 100644 --- a/tests/test_sdp.c +++ b/tests/test_sdp.c @@ -88,13 +88,38 @@ static int validate_m_line(struct ast_test *test, const struct ast_sdp_m_line *m return -1; } + if (m_line->port == 0) { + ast_test_status_update(test, "Expected %s m-line to not be declined\n", + media_type); + return -1; + } + if (ast_sdp_m_get_payload_count(m_line) != num_payloads) { - ast_test_status_update(test, "Expected m-line payload count %d but got %d\n", - num_payloads, ast_sdp_m_get_payload_count(m_line)); + ast_test_status_update(test, "Expected %s m-line payload count %d but got %d\n", + media_type, num_payloads, ast_sdp_m_get_payload_count(m_line)); + return -1; + } + + ast_test_status_update(test, "SDP %s m-line is as expected\n", media_type); + return 0; +} + +static int validate_m_line_declined(struct ast_test *test, + const struct ast_sdp_m_line *m_line, const char *media_type) +{ + if (strcmp(m_line->type, media_type)) { + ast_test_status_update(test, "Expected m-line media type %s but got %s\n", + media_type, m_line->type); + return -1; + } + + if (m_line->port != 0) { + ast_test_status_update(test, "Expected %s m-line to be declined but got port %u\n", + media_type, m_line->port); return -1; } - ast_test_status_update(test, "SDP m-line is as expected\n"); + ast_test_status_update(test, "SDP %s m-line is as expected\n", media_type); return 0; } @@ -438,6 +463,29 @@ struct sdp_format { const char *formats; }; +static int build_sdp_option_formats(struct ast_sdp_options *options, int num_streams, const struct sdp_format *formats) +{ + int idx; + + for (idx = 0; idx < num_streams; ++idx) { + struct ast_format_cap *caps; + + if (ast_strlen_zero(formats[idx].formats)) { + continue; + } + + caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!caps + || ast_format_cap_update_by_allow_disallow(caps, formats[idx].formats, 1) < 0) { + ao2_cleanup(caps); + return -1; + } + ast_sdp_options_set_format_cap_type(options, formats[idx].type, caps); + ao2_cleanup(caps); + } + return 0; +} + /*! * \brief Common method to build an SDP state for a test. * @@ -450,9 +498,18 @@ struct sdp_format { * * \param num_streams The number of elements in the formats array. * \param formats Array of media types and formats that will be in the state. + * \param opt_num_streams The number of new stream types allowed to create. + * Not used if test_options provided. + * \param opt_formats Array of new stream media types and formats allowed to create. + * NULL if use a default stream creation. + * Not used if test_options provided. + * \param max_streams 0 if set max to max(3, num_streams) else max(max_streams, num_streams) + * Not used if test_options provided. * \param test_options Optional SDP options. */ -static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_format *formats, struct ast_sdp_options *test_options) +static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_format *formats, + int opt_num_streams, const struct sdp_format *opt_formats, unsigned int max_streams, + struct ast_sdp_options *test_options) { struct ast_stream_topology *topology = NULL; struct ast_sdp_state *state = NULL; @@ -460,10 +517,34 @@ static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_f int i; if (!test_options) { + static const struct sdp_format sdp_formats[] = { + { AST_MEDIA_TYPE_AUDIO, "ulaw" }, + { AST_MEDIA_TYPE_VIDEO, "vp8" }, + { AST_MEDIA_TYPE_IMAGE, "t38" }, + }; + options = sdp_options_common(); if (!options) { goto end; } + + /* Determine max_streams to allow */ + if (!max_streams) { + max_streams = ARRAY_LEN(sdp_formats); + } + if (max_streams < num_streams) { + max_streams = num_streams; + } + ast_sdp_options_set_max_streams(options, max_streams); + + /* Determine new stream formats and types allowed */ + if (!opt_formats) { + opt_num_streams = ARRAY_LEN(sdp_formats); + opt_formats = sdp_formats; + } + if (build_sdp_option_formats(options, opt_num_streams, opt_formats)) { + goto end; + } } else { options = test_options; } @@ -474,22 +555,31 @@ static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_f } for (i = 0; i < num_streams; ++i) { - RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup); struct ast_stream *stream; - caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); - if (!caps) { + stream = ast_stream_alloc("sure_thing", formats[i].type); + if (!stream) { goto end; } - if (ast_format_cap_update_by_allow_disallow(caps, formats[i].formats, 1) < 0) { - goto end; + if (!ast_strlen_zero(formats[i].formats)) { + struct ast_format_cap *caps; + + caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!caps + || ast_format_cap_update_by_allow_disallow(caps, formats[i].formats, 1) < 0) { + ao2_cleanup(caps); + ast_stream_free(stream); + goto end; + } + ast_stream_set_formats(stream, caps); + ao2_cleanup(caps); + } else { + ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED); } - stream = ast_stream_alloc("sure_thing", formats[i].type); - if (!stream) { + if (ast_stream_topology_append_stream(topology, stream) < 0) { + ast_stream_free(stream); goto end; } - ast_stream_set_formats(stream, caps); - ast_stream_topology_append_stream(topology, stream); } state = ast_sdp_state_alloc(topology, options); @@ -530,7 +620,8 @@ AST_TEST_DEFINE(topology_to_sdp) break; } - sdp_state = build_sdp_state(ARRAY_LEN(formats), formats, NULL); + sdp_state = build_sdp_state(ARRAY_LEN(formats), formats, + ARRAY_LEN(formats), formats, 0, NULL); if (!sdp_state) { goto end; } @@ -674,7 +765,8 @@ AST_TEST_DEFINE(sdp_to_topology) break; } - sdp_state = build_sdp_state(ARRAY_LEN(sdp_formats), sdp_formats, NULL); + sdp_state = build_sdp_state(ARRAY_LEN(sdp_formats), sdp_formats, + ARRAY_LEN(sdp_formats), sdp_formats, 0, NULL); if (!sdp_state) { res = AST_TEST_FAIL; goto end; @@ -723,7 +815,7 @@ end: return res; } -static int validate_merged_sdp(struct ast_test *test, const struct ast_sdp *sdp) +static int validate_avi_sdp_streams(struct ast_test *test, const struct ast_sdp *sdp) { struct ast_sdp_m_line *m_line; @@ -769,7 +861,12 @@ static int validate_merged_sdp(struct ast_test *test, const struct ast_sdp *sdp) return 0; } -AST_TEST_DEFINE(sdp_merge_symmetric) +static enum ast_test_result_state sdp_negotiation_completed_tests(struct ast_test *test, + int offer_num_streams, const struct sdp_format *offer_formats, + int answer_num_streams, const struct sdp_format *answer_formats, + int allowed_ans_num_streams, const struct sdp_format *allowed_ans_formats, + unsigned int max_streams, + int (*validate_sdp)(struct ast_test *test, const struct ast_sdp *sdp)) { enum ast_test_result_state res = AST_TEST_PASS; struct ast_sdp_state *sdp_state_offerer = NULL; @@ -777,65 +874,68 @@ AST_TEST_DEFINE(sdp_merge_symmetric) const struct ast_sdp *offerer_sdp; const struct ast_sdp *answerer_sdp; - static const struct sdp_format offerer_formats[] = { - { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" }, - { AST_MEDIA_TYPE_VIDEO, "h264,vp8" }, - { AST_MEDIA_TYPE_IMAGE, "t38" }, - }; - static const struct sdp_format answerer_formats[] = { - { AST_MEDIA_TYPE_AUDIO, "ulaw" }, - { AST_MEDIA_TYPE_VIDEO, "vp8" }, - { AST_MEDIA_TYPE_IMAGE, "t38" }, - }; - - switch(cmd) { - case TEST_INIT: - info->name = "sdp_merge_symmetric"; - info->category = "/main/sdp/"; - info->summary = "Merge two SDPs with symmetric stream types"; - info->description = - "SDPs 1 and 2 each have one audio and one video stream (in that order).\n" - "SDP 1 offers to SDP 2, who answers. We ensure that both local SDPs have\n" - "the expected stream types and the expected formats"; - return AST_TEST_NOT_RUN; - case TEST_EXECUTE: - break; - } - - sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats, NULL); + sdp_state_offerer = build_sdp_state(offer_num_streams, offer_formats, + offer_num_streams, offer_formats, max_streams, NULL); if (!sdp_state_offerer) { + ast_test_status_update(test, "Building offerer SDP state failed\n"); res = AST_TEST_FAIL; goto end; } - sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats, NULL); + sdp_state_answerer = build_sdp_state(answer_num_streams, answer_formats, + allowed_ans_num_streams, allowed_ans_formats, max_streams, NULL); if (!sdp_state_answerer) { + ast_test_status_update(test, "Building answerer SDP state failed\n"); res = AST_TEST_FAIL; goto end; } offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer); if (!offerer_sdp) { + ast_test_status_update(test, "Building offerer offer failed\n"); res = AST_TEST_FAIL; goto end; } - ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp); + if (ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp)) { + ast_test_status_update(test, "Setting answerer offer failed\n"); + res = AST_TEST_FAIL; + goto end; + } answerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_answerer); if (!answerer_sdp) { + ast_test_status_update(test, "Building answerer answer failed\n"); res = AST_TEST_FAIL; goto end; } - ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp); + if (ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp)) { + ast_test_status_update(test, "Setting offerer answer failed\n"); + res = AST_TEST_FAIL; + goto end; + } - /* Get the offerer SDP again because it's now going to be the joint SDP */ + /* + * Restart SDP negotiations to build the joint SDP on the offerer + * side. Otherwise we will get the original offer for use in + * case of retransmissions. + */ + if (ast_sdp_state_restart_negotiations(sdp_state_offerer)) { + ast_test_status_update(test, "Restarting negotiations failed\n"); + res = AST_TEST_FAIL; + goto end; + } offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer); - if (validate_merged_sdp(test, offerer_sdp)) { + if (!offerer_sdp) { + ast_test_status_update(test, "Building offerer current sdp failed\n"); + res = AST_TEST_FAIL; + goto end; + } + if (validate_sdp(test, offerer_sdp)) { res = AST_TEST_FAIL; goto end; } - if (validate_merged_sdp(test, answerer_sdp)) { + if (validate_sdp(test, answerer_sdp)) { res = AST_TEST_FAIL; goto end; } @@ -847,14 +947,38 @@ end: return res; } -AST_TEST_DEFINE(sdp_merge_crisscross) +AST_TEST_DEFINE(sdp_negotiation_initial) { - enum ast_test_result_state res = AST_TEST_PASS; - struct ast_sdp_state *sdp_state_offerer = NULL; - struct ast_sdp_state *sdp_state_answerer = NULL; - const struct ast_sdp *offerer_sdp; - const struct ast_sdp *answerer_sdp; + static const struct sdp_format offerer_formats[] = { + { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" }, + { AST_MEDIA_TYPE_VIDEO, "h264,vp8" }, + { AST_MEDIA_TYPE_IMAGE, "t38" }, + }; + switch(cmd) { + case TEST_INIT: + info->name = "sdp_negotiation_initial"; + info->category = "/main/sdp/"; + info->summary = "Simulate an initial negotiation"; + info->description = + "Initial negotiation tests creating new streams on the answering side.\n" + "After negotiation both offerer and answerer sides should have the same\n" + "expected stream types and formats."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + return sdp_negotiation_completed_tests(test, + ARRAY_LEN(offerer_formats), offerer_formats, + 0, NULL, + 0, NULL, + 0, + validate_avi_sdp_streams); +} + +AST_TEST_DEFINE(sdp_negotiation_type_change) +{ static const struct sdp_format offerer_formats[] = { { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" }, { AST_MEDIA_TYPE_VIDEO, "h264,vp8" }, @@ -868,82 +992,66 @@ AST_TEST_DEFINE(sdp_merge_crisscross) switch(cmd) { case TEST_INIT: - info->name = "sdp_merge_crisscross"; + info->name = "sdp_negotiation_type_change"; info->category = "/main/sdp/"; - info->summary = "Merge two SDPs with symmetric stream types"; + info->summary = "Simulate a re-negotiation changing stream types"; info->description = - "SDPs 1 and 2 each have one audio and one video stream. However, SDP 1 and\n" - "2 natively have the formats in a different order.\n" - "SDP 1 offers to SDP 2, who answers. We ensure that both local SDPs have\n" - "the expected stream types and the expected formats. Since SDP 1 was the\n" - "offerer, the format order on SDP 1 should determine the order of formats in the SDPs"; + "Reinvite negotiation tests changing stream types on the answering side.\n" + "After negotiation both offerer and answerer sides should have the same\n" + "expected stream types and formats."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } - sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats, NULL); - if (!sdp_state_offerer) { - res = AST_TEST_FAIL; - goto end; - } + return sdp_negotiation_completed_tests(test, + ARRAY_LEN(offerer_formats), offerer_formats, + ARRAY_LEN(answerer_formats), answerer_formats, + 0, NULL, + 0, + validate_avi_sdp_streams); +} - sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats, NULL); - if (!sdp_state_answerer) { - res = AST_TEST_FAIL; - goto end; - } +static int validate_aviavia_declined_sdp_streams(struct ast_test *test, const struct ast_sdp *sdp) +{ + struct ast_sdp_m_line *m_line; - offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer); - if (!offerer_sdp) { - res = AST_TEST_FAIL; - goto end; + if (!sdp) { + return -1; } - ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp); - answerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_answerer); - if (!answerer_sdp) { - res = AST_TEST_FAIL; - goto end; + m_line = ast_sdp_get_m(sdp, 0); + if (validate_m_line_declined(test, m_line, "audio")) { + return -1; } - ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp); - - /* Get the offerer SDP again because it's now going to be the joint SDP */ - offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer); - if (validate_merged_sdp(test, offerer_sdp)) { - res = AST_TEST_FAIL; - goto end; - } - if (validate_merged_sdp(test, answerer_sdp)) { - res = AST_TEST_FAIL; - goto end; + m_line = ast_sdp_get_m(sdp, 1); + if (validate_m_line_declined(test, m_line, "video")) { + return -1; } -end: - ast_sdp_state_free(sdp_state_offerer); - ast_sdp_state_free(sdp_state_answerer); - - return res; -} + m_line = ast_sdp_get_m(sdp, 2); + if (validate_m_line_declined(test, m_line, "image")) { + return -1; + } -static int validate_merged_sdp_asymmetric(struct ast_test *test, const struct ast_sdp *sdp, int is_offer) -{ - struct ast_sdp_m_line *m_line; - const char *side = is_offer ? "Offer side" : "Answer side"; + m_line = ast_sdp_get_m(sdp, 3); + if (validate_m_line_declined(test, m_line, "audio")) { + return -1; + } - if (!sdp) { - ast_test_status_update(test, "%s does not have a SDP\n", side); + m_line = ast_sdp_get_m(sdp, 4); + if (validate_m_line_declined(test, m_line, "video")) { return -1; } - /* Stream 0 */ - m_line = ast_sdp_get_m(sdp, 0); - if (validate_m_line(test, m_line, "audio", 1)) { + m_line = ast_sdp_get_m(sdp, 5); + if (validate_m_line_declined(test, m_line, "image")) { return -1; } - if (!m_line->port) { - ast_test_status_update(test, "%s stream %d does%s have a port\n", side, 0, "n't"); + + m_line = ast_sdp_get_m(sdp, 6); + if (validate_m_line(test, m_line, "audio", 1)) { return -1; } if (validate_rtpmap(test, m_line, "PCMU")) { @@ -954,88 +1062,154 @@ static int validate_merged_sdp_asymmetric(struct ast_test *test, const struct as if (!validate_rtpmap(test, m_line, "PCMA")) { return -1; } - if (!validate_rtpmap(test, m_line, "G722")) { - return -1; + + return 0; +} + +AST_TEST_DEFINE(sdp_negotiation_decline_incompatible) +{ + static const struct sdp_format offerer_formats[] = { + /* Incompatible declined streams */ + { AST_MEDIA_TYPE_AUDIO, "alaw" }, + { AST_MEDIA_TYPE_VIDEO, "vp8" }, + { AST_MEDIA_TYPE_IMAGE, "t38" }, + /* Initially declined streams */ + { AST_MEDIA_TYPE_AUDIO, "" }, + { AST_MEDIA_TYPE_VIDEO, "" }, + { AST_MEDIA_TYPE_IMAGE, "" }, + /* Compatible stream so not all are declined */ + { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw" }, + }; + static const struct sdp_format allowed_formats[] = { + { AST_MEDIA_TYPE_AUDIO, "ulaw" }, + }; + + switch(cmd) { + case TEST_INIT: + info->name = "sdp_negotiation_decline_incompatible"; + info->category = "/main/sdp/"; + info->summary = "Simulate an initial negotiation declining streams"; + info->description = + "Initial negotiation tests declining incompatible streams.\n" + "After negotiation both offerer and answerer sides should have\n" + "the same expected stream types and formats."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; } - if (!validate_rtpmap(test, m_line, "opus")) { + + return sdp_negotiation_completed_tests(test, + ARRAY_LEN(offerer_formats), offerer_formats, + 0, NULL, + ARRAY_LEN(allowed_formats), allowed_formats, + ARRAY_LEN(offerer_formats), + validate_aviavia_declined_sdp_streams); +} + +static int validate_aaaa_declined_sdp_streams(struct ast_test *test, const struct ast_sdp *sdp) +{ + struct ast_sdp_m_line *m_line; + + if (!sdp) { return -1; } - /* The remaining streams should be declined */ + m_line = ast_sdp_get_m(sdp, 0); + if (validate_m_line(test, m_line, "audio", 1)) { + return -1; + } + if (validate_rtpmap(test, m_line, "PCMU")) { + return -1; + } - /* Stream 1 */ m_line = ast_sdp_get_m(sdp, 1); if (validate_m_line(test, m_line, "audio", 1)) { return -1; } - if (m_line->port) { - ast_test_status_update(test, "%s stream %d does%s have a port\n", side, 1, ""); + if (validate_rtpmap(test, m_line, "PCMU")) { return -1; } - /* Stream 2 */ m_line = ast_sdp_get_m(sdp, 2); - if (validate_m_line(test, m_line, "video", 1)) { + if (validate_m_line(test, m_line, "audio", 1)) { return -1; } - if (m_line->port) { - ast_test_status_update(test, "%s stream %d does%s have a port\n", side, 2, ""); + if (validate_rtpmap(test, m_line, "PCMU")) { return -1; } - /* Stream 3 */ m_line = ast_sdp_get_m(sdp, 3); - if (validate_m_line(test, m_line, "image", 1)) { - return -1; - } - if (m_line->port) { - ast_test_status_update(test, "%s stream %d does%s have a port\n", side, 3, ""); + if (validate_m_line_declined(test, m_line, "audio")) { return -1; } return 0; } -AST_TEST_DEFINE(sdp_merge_asymmetric) +AST_TEST_DEFINE(sdp_negotiation_decline_max_streams) +{ + static const struct sdp_format offerer_formats[] = { + { AST_MEDIA_TYPE_AUDIO, "ulaw" }, + { AST_MEDIA_TYPE_AUDIO, "ulaw" }, + { AST_MEDIA_TYPE_AUDIO, "ulaw" }, + { AST_MEDIA_TYPE_AUDIO, "ulaw" }, + }; + + switch(cmd) { + case TEST_INIT: + info->name = "sdp_negotiation_decline_max_streams"; + info->category = "/main/sdp/"; + info->summary = "Simulate an initial negotiation declining excessive streams"; + info->description = + "Initial negotiation tests declining too many streams on the answering side.\n" + "After negotiation both offerer and answerer sides should have the same\n" + "expected stream types and formats."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + return sdp_negotiation_completed_tests(test, + ARRAY_LEN(offerer_formats), offerer_formats, + 0, NULL, + 0, NULL, + 0, + validate_aaaa_declined_sdp_streams); +} + +AST_TEST_DEFINE(sdp_negotiation_not_acceptable) { enum ast_test_result_state res = AST_TEST_PASS; struct ast_sdp_state *sdp_state_offerer = NULL; struct ast_sdp_state *sdp_state_answerer = NULL; const struct ast_sdp *offerer_sdp; - const struct ast_sdp *answerer_sdp; static const struct sdp_format offerer_formats[] = { - { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" }, - { AST_MEDIA_TYPE_AUDIO, "ulaw" }, - { AST_MEDIA_TYPE_VIDEO, "h261" }, - { AST_MEDIA_TYPE_IMAGE, "t38" }, - }; - static const struct sdp_format answerer_formats[] = { - { AST_MEDIA_TYPE_AUDIO, "ulaw" }, + { AST_MEDIA_TYPE_AUDIO, "alaw" }, + { AST_MEDIA_TYPE_AUDIO, "alaw" }, }; switch(cmd) { case TEST_INIT: - info->name = "sdp_merge_asymmetric"; + info->name = "sdp_negotiation_not_acceptable"; info->category = "/main/sdp/"; - info->summary = "Merge two SDPs with an asymmetric number of streams"; + info->summary = "Simulate an initial negotiation declining all streams"; info->description = - "SDP 1 offers a four stream topology: Audio,Audio,Video,T.38\n" - "SDP 2 only has a single audio stream topology\n" - "We ensure that both local SDPs have the expected stream types and\n" - "the expected declined streams"; + "Initial negotiation tests declining all streams for a 488 on the answering side.\n" + "Negotiations should fail because there are no acceptable streams."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } - sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats, NULL); + sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats, + ARRAY_LEN(offerer_formats), offerer_formats, 0, NULL); if (!sdp_state_offerer) { res = AST_TEST_FAIL; goto end; } - sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats, NULL); + sdp_state_answerer = build_sdp_state(0, NULL, 0, NULL, 0, NULL); if (!sdp_state_answerer) { res = AST_TEST_FAIL; goto end; @@ -1047,24 +1221,15 @@ AST_TEST_DEFINE(sdp_merge_asymmetric) goto end; } - ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp); - answerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_answerer); - if (!answerer_sdp) { + if (!ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp)) { + ast_test_status_update(test, "Bad. Setting remote SDP was successful.\n"); res = AST_TEST_FAIL; goto end; } - - ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp); - -#if defined(XXX_TODO_NEED_TO_HANDLE_DECLINED_STREAMS_ON_OFFER_SIDE) - /* Get the offerer SDP again because it's now going to be the joint SDP */ - offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer); - if (validate_merged_sdp_asymmetric(test, offerer_sdp, 1)) { - res = AST_TEST_FAIL; - } -#endif - if (validate_merged_sdp_asymmetric(test, answerer_sdp, 0)) { + if (!ast_sdp_state_is_offer_rejected(sdp_state_answerer)) { + ast_test_status_update(test, "Bad. Negotiation failed for some other reason.\n"); res = AST_TEST_FAIL; + goto end; } end: @@ -1135,9 +1300,12 @@ AST_TEST_DEFINE(sdp_ssrc_attributes) ast_test_status_update(test, "Failed to allocate SDP options\n"); goto end; } + if (build_sdp_option_formats(options, ARRAY_LEN(formats), formats)) { + goto end; + } ast_sdp_options_set_ssrc(options, 1); - test_state = build_sdp_state(ARRAY_LEN(formats), formats, options); + test_state = build_sdp_state(ARRAY_LEN(formats), formats, 0, NULL, 0, options); if (!test_state) { ast_test_status_update(test, "Failed to create SDP state\n"); goto end; @@ -1179,6 +1347,725 @@ end: return res; } +struct sdp_topology_stream { + /*! Media stream type: audio, video, image */ + enum ast_media_type type; + /*! Media stream state: removed/declined, sendrecv */ + enum ast_stream_state state; + /*! Comma separated list of formats allowed on the stream. Can be NULL if stream is removed/declined. */ + const char *formats; + /*! Optional name of stream. NULL for default name. */ + const char *name; +}; + +struct sdp_update_test { + /*! Maximum number of streams. (0 if default) */ + int max_streams; + /*! Optional initial SDP state topology (NULL if not present) */ + const struct sdp_topology_stream * const *initial; + /*! Required first topology update */ + const struct sdp_topology_stream * const *update_1; + /*! Optional second topology update (NULL if not present) */ + const struct sdp_topology_stream * const *update_2; + /*! Expected topology to be offered */ + const struct sdp_topology_stream * const *expected; +}; + +static struct ast_stream_topology *build_update_topology(const struct sdp_topology_stream * const *spec) +{ + struct ast_stream_topology *topology; + const struct sdp_topology_stream *desc; + + topology = ast_stream_topology_alloc(); + if (!topology) { + return NULL; + } + + for (desc = *spec; desc; ++spec, desc = *spec) { + struct ast_stream *stream; + const char *name; + + name = desc->name ?: ast_codec_media_type2str(desc->type); + stream = ast_stream_alloc(name, desc->type); + if (!stream) { + goto fail; + } + ast_stream_set_state(stream, desc->state); + if (desc->formats) { + struct ast_format_cap *caps; + + caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!caps + || ast_format_cap_update_by_allow_disallow(caps, desc->formats, 1) < 0) { + ao2_cleanup(caps); + ast_stream_free(stream); + goto fail; + } + ast_stream_set_formats(stream, caps); + ao2_ref(caps, -1); + } + if (ast_stream_topology_append_stream(topology, stream) < 0) { + ast_stream_free(stream); + goto fail; + } + } + return topology; + +fail: + ast_stream_topology_free(topology); + return NULL; +} + +static int cmp_update_topology(struct ast_test *test, + const struct ast_stream_topology *expected, const struct ast_stream_topology *merged) +{ + int status = 0; + int idx; + int max_streams; + struct ast_stream *exp_stream; + struct ast_stream *mrg_stream; + + idx = ast_stream_topology_get_count(expected); + max_streams = ast_stream_topology_get_count(merged); + if (idx != max_streams) { + ast_test_status_update(test, "Expected %d streams got %d streams\n", + idx, max_streams); + status = -1; + } + if (idx < max_streams) { + max_streams = idx; + } + + /* Compare common streams by position */ + for (idx = 0; idx < max_streams; ++idx) { + exp_stream = ast_stream_topology_get_stream(expected, idx); + mrg_stream = ast_stream_topology_get_stream(merged, idx); + + if (strcmp(ast_stream_get_name(exp_stream), ast_stream_get_name(mrg_stream))) { + ast_test_status_update(test, + "Stream %d: Expected stream name '%s' got stream name '%s'\n", + idx, + ast_stream_get_name(exp_stream), + ast_stream_get_name(mrg_stream)); + status = -1; + } + + if (ast_stream_get_state(exp_stream) != ast_stream_get_state(mrg_stream)) { + ast_test_status_update(test, + "Stream %d: Expected stream state '%s' got stream state '%s'\n", + idx, + ast_stream_state2str(ast_stream_get_state(exp_stream)), + ast_stream_state2str(ast_stream_get_state(mrg_stream))); + status = -1; + } + + if (ast_stream_get_type(exp_stream) != ast_stream_get_type(mrg_stream)) { + ast_test_status_update(test, + "Stream %d: Expected stream type '%s' got stream type '%s'\n", + idx, + ast_codec_media_type2str(ast_stream_get_type(exp_stream)), + ast_codec_media_type2str(ast_stream_get_type(mrg_stream))); + status = -1; + continue; + } + + if (ast_stream_get_state(exp_stream) == AST_STREAM_STATE_REMOVED + || ast_stream_get_state(mrg_stream) == AST_STREAM_STATE_REMOVED) { + /* + * Cannot compare formats if one of the streams is + * declined because there may not be any on the declined + * stream. + */ + continue; + } + if (!ast_format_cap_identical(ast_stream_get_formats(exp_stream), + ast_stream_get_formats(mrg_stream))) { + ast_test_status_update(test, + "Stream %d: Expected formats do not match merged formats\n", + idx); + status = -1; + } + } + + return status; +} + + +static const struct sdp_topology_stream audio_declined_no_name = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_REMOVED, NULL, NULL +}; + +static const struct sdp_topology_stream audio_ulaw_no_name = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "ulaw", NULL +}; + +static const struct sdp_topology_stream audio_alaw_no_name = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "alaw", NULL +}; + +static const struct sdp_topology_stream audio_g722_no_name = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "g722", NULL +}; + +static const struct sdp_topology_stream audio_g723_no_name = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "g723", NULL +}; + +static const struct sdp_topology_stream video_declined_no_name = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, NULL, NULL +}; + +static const struct sdp_topology_stream video_h261_no_name = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h261", NULL +}; + +static const struct sdp_topology_stream video_h263_no_name = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h263", NULL +}; + +static const struct sdp_topology_stream video_h264_no_name = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h264", NULL +}; + +static const struct sdp_topology_stream video_vp8_no_name = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "vp8", NULL +}; + +static const struct sdp_topology_stream image_declined_no_name = { + AST_MEDIA_TYPE_IMAGE, AST_STREAM_STATE_REMOVED, NULL, NULL +}; + +static const struct sdp_topology_stream image_t38_no_name = { + AST_MEDIA_TYPE_IMAGE, AST_STREAM_STATE_SENDRECV, "t38", NULL +}; + + +static const struct sdp_topology_stream *top_ulaw_alaw_h264__vp8[] = { + &audio_ulaw_no_name, + &audio_alaw_no_name, + &video_h264_no_name, + &video_vp8_no_name, + NULL +}; + +static const struct sdp_topology_stream *top__vp8_alaw_h264_ulaw[] = { + &video_vp8_no_name, + &audio_alaw_no_name, + &video_h264_no_name, + &audio_ulaw_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_alaw_ulaw__vp8_h264[] = { + &audio_alaw_no_name, + &audio_ulaw_no_name, + &video_vp8_no_name, + &video_h264_no_name, + NULL +}; + +/* Sorting by type with no new or deleted streams */ +static const struct sdp_update_test mrg_by_type_00 = { + .initial = top_ulaw_alaw_h264__vp8, + .update_1 = top__vp8_alaw_h264_ulaw, + .expected = top_alaw_ulaw__vp8_h264, +}; + + +static const struct sdp_topology_stream *top_alaw__vp8[] = { + &audio_alaw_no_name, + &video_vp8_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_h264__vp8_ulaw[] = { + &video_h264_no_name, + &video_vp8_no_name, + &audio_ulaw_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_ulaw_h264__vp8[] = { + &audio_ulaw_no_name, + &video_h264_no_name, + &video_vp8_no_name, + NULL +}; + +/* Sorting by type and adding a stream */ +static const struct sdp_update_test mrg_by_type_01 = { + .initial = top_alaw__vp8, + .update_1 = top_h264__vp8_ulaw, + .expected = top_ulaw_h264__vp8, +}; + + +static const struct sdp_topology_stream *top_alaw__vp8_vdec[] = { + &audio_alaw_no_name, + &video_vp8_no_name, + &video_declined_no_name, + NULL +}; + +/* Sorting by type and deleting a stream */ +static const struct sdp_update_test mrg_by_type_02 = { + .initial = top_ulaw_h264__vp8, + .update_1 = top_alaw__vp8, + .expected = top_alaw__vp8_vdec, +}; + + +static const struct sdp_topology_stream *top_h264_alaw_ulaw[] = { + &video_h264_no_name, + &audio_alaw_no_name, + &audio_ulaw_no_name, + NULL +}; + +static const struct sdp_topology_stream *top__t38[] = { + &image_t38_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_vdec__t38_adec[] = { + &video_declined_no_name, + &image_t38_no_name, + &audio_declined_no_name, + NULL +}; + +/* Sorting by type changing stream types for T.38 */ +static const struct sdp_update_test mrg_by_type_03 = { + .initial = top_h264_alaw_ulaw, + .update_1 = top__t38, + .expected = top_vdec__t38_adec, +}; + + +/* Sorting by type changing stream types back from T.38 */ +static const struct sdp_update_test mrg_by_type_04 = { + .initial = top_vdec__t38_adec, + .update_1 = top_h264_alaw_ulaw, + .expected = top_h264_alaw_ulaw, +}; + + +static const struct sdp_topology_stream *top_h264[] = { + &video_h264_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_vdec__t38[] = { + &video_declined_no_name, + &image_t38_no_name, + NULL +}; + +/* Sorting by type changing stream types for T.38 */ +static const struct sdp_update_test mrg_by_type_05 = { + .initial = top_h264, + .update_1 = top__t38, + .expected = top_vdec__t38, +}; + + +static const struct sdp_topology_stream *top_h264_idec[] = { + &video_h264_no_name, + &image_declined_no_name, + NULL +}; + +/* Sorting by type changing stream types back from T.38 */ +static const struct sdp_update_test mrg_by_type_06 = { + .initial = top_vdec__t38, + .update_1 = top_h264, + .expected = top_h264_idec, +}; + + +static const struct sdp_topology_stream *top_ulaw_adec_h264__vp8[] = { + &audio_ulaw_no_name, + &audio_declined_no_name, + &video_h264_no_name, + &video_vp8_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_h263_alaw_h261_h264_vp8[] = { + &video_h263_no_name, + &audio_alaw_no_name, + &video_h261_no_name, + &video_h264_no_name, + &video_vp8_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_alaw_h264_h263_h261_vp8[] = { + &audio_alaw_no_name, + &video_h264_no_name, + &video_h263_no_name, + &video_h261_no_name, + &video_vp8_no_name, + NULL +}; + +/* Sorting by type with backfill and adding streams */ +static const struct sdp_update_test mrg_by_type_07 = { + .initial = top_ulaw_adec_h264__vp8, + .update_1 = top_h263_alaw_h261_h264_vp8, + .expected = top_alaw_h264_h263_h261_vp8, +}; + + +static const struct sdp_topology_stream *top_ulaw_alaw_h264__vp8_h261[] = { + &audio_ulaw_no_name, + &audio_alaw_no_name, + &video_h264_no_name, + &video_vp8_no_name, + &video_h261_no_name, + NULL +}; + +/* Sorting by type overlimit of 4 and drop */ +static const struct sdp_update_test mrg_by_type_08 = { + .max_streams = 4, + .initial = top_ulaw_alaw_h264__vp8, + .update_1 = top_ulaw_alaw_h264__vp8_h261, + .expected = top_ulaw_alaw_h264__vp8, +}; + + +static const struct sdp_topology_stream *top_ulaw_alaw_h264[] = { + &audio_ulaw_no_name, + &audio_alaw_no_name, + &video_h264_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_alaw_h261__vp8[] = { + &audio_alaw_no_name, + &video_h261_no_name, + &video_vp8_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_alaw_adec_h261__vp8[] = { + &audio_alaw_no_name, + &audio_declined_no_name, + &video_h261_no_name, + &video_vp8_no_name, + NULL +}; + +/* Sorting by type with delete and add of streams */ +static const struct sdp_update_test mrg_by_type_09 = { + .initial = top_ulaw_alaw_h264, + .update_1 = top_alaw_h261__vp8, + .expected = top_alaw_adec_h261__vp8, +}; + + +static const struct sdp_topology_stream *top_ulaw_adec_h264[] = { + &audio_ulaw_no_name, + &audio_declined_no_name, + &video_h264_no_name, + NULL +}; + +/* Sorting by type and adding streams */ +static const struct sdp_update_test mrg_by_type_10 = { + .initial = top_ulaw_adec_h264, + .update_1 = top_alaw_ulaw__vp8_h264, + .expected = top_alaw_ulaw__vp8_h264, +}; + + +static const struct sdp_topology_stream *top_adec_g722_h261[] = { + &audio_declined_no_name, + &audio_g722_no_name, + &video_h261_no_name, + NULL +}; + +/* Sorting by type and deleting old streams */ +static const struct sdp_update_test mrg_by_type_11 = { + .initial = top_ulaw_alaw_h264, + .update_1 = top_adec_g722_h261, + .expected = top_adec_g722_h261, +}; + + +static const struct sdp_topology_stream audio_alaw4dave = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "alaw", "dave" +}; + +static const struct sdp_topology_stream audio_g7224dave = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "g722", "dave" +}; + +static const struct sdp_topology_stream audio_ulaw4fred = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "ulaw", "fred" +}; + +static const struct sdp_topology_stream audio_alaw4fred = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "alaw", "fred" +}; + +static const struct sdp_topology_stream audio_ulaw4rose = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "ulaw", "rose" +}; + +static const struct sdp_topology_stream audio_g7224rose = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "g722", "rose" +}; + + +static const struct sdp_topology_stream video_h2614dave = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h261", "dave" +}; + +static const struct sdp_topology_stream video_h2634dave = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h263", "dave" +}; + +static const struct sdp_topology_stream video_h2634fred = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h263", "fred" +}; + +static const struct sdp_topology_stream video_h2644fred = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h264", "fred" +}; + +static const struct sdp_topology_stream video_h2644rose = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h264", "rose" +}; + +static const struct sdp_topology_stream video_h2614rose = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h261", "rose" +}; + + +static const struct sdp_topology_stream *top_adave_alaw_afred_ulaw_arose_g722_vdave_h261_vfred_h263_vrose_h264[] = { + &audio_alaw4dave, + &audio_alaw_no_name, + &audio_ulaw4fred, + &audio_ulaw_no_name, + &audio_g7224rose, + &audio_g722_no_name, + &video_h2614dave, + &video_h261_no_name, + &video_h2634fred, + &video_h263_no_name, + &video_h2644rose, + &video_h264_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_vfred_vrose_vdave_h263_h264_h261_afred_ulaw_arose_g722_adave_alaw[] = { + &video_h2644fred, + &video_h2614rose, + &video_h2634dave, + &video_h263_no_name, + &video_h264_no_name, + &video_h261_no_name, + &audio_alaw4fred, + &audio_ulaw_no_name, + &audio_ulaw4rose, + &audio_g722_no_name, + &audio_g7224dave, + &audio_alaw_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_adave_ulaw_afred_g722_arose_alaw_vdave_h263_vfred_h264_vrose_h261[] = { + &audio_g7224dave, + &audio_ulaw_no_name, + &audio_alaw4fred, + &audio_g722_no_name, + &audio_ulaw4rose, + &audio_alaw_no_name, + &video_h2634dave, + &video_h263_no_name, + &video_h2644fred, + &video_h264_no_name, + &video_h2614rose, + &video_h261_no_name, + NULL +}; + +/* Sorting by name and type with no new or deleted streams */ +static const struct sdp_update_test mrg_by_name_00 = { + .initial = top_adave_alaw_afred_ulaw_arose_g722_vdave_h261_vfred_h263_vrose_h264, + .update_1 = top_vfred_vrose_vdave_h263_h264_h261_afred_ulaw_arose_g722_adave_alaw, + .expected = top_adave_ulaw_afred_g722_arose_alaw_vdave_h263_vfred_h264_vrose_h261, +}; + + +static const struct sdp_topology_stream *top_adave_g723_h261[] = { + &audio_g7224dave, + &audio_g723_no_name, + &video_h261_no_name, + NULL +}; + +/* Sorting by name and type adding names to streams */ +static const struct sdp_update_test mrg_by_name_01 = { + .initial = top_ulaw_alaw_h264, + .update_1 = top_adave_g723_h261, + .expected = top_adave_g723_h261, +}; + + +/* Sorting by name and type removing names from streams */ +static const struct sdp_update_test mrg_by_name_02 = { + .initial = top_adave_g723_h261, + .update_1 = top_ulaw_alaw_h264, + .expected = top_ulaw_alaw_h264, +}; + + +static const struct sdp_update_test *sdp_update_cases[] = { + /* Merging by type */ + /* 00 */ &mrg_by_type_00, + /* 01 */ &mrg_by_type_01, + /* 02 */ &mrg_by_type_02, + /* 03 */ &mrg_by_type_03, + /* 04 */ &mrg_by_type_04, + /* 05 */ &mrg_by_type_05, + /* 06 */ &mrg_by_type_06, + /* 07 */ &mrg_by_type_07, + /* 08 */ &mrg_by_type_08, + /* 09 */ &mrg_by_type_09, + /* 10 */ &mrg_by_type_10, + /* 11 */ &mrg_by_type_11, + + /* Merging by name and type */ + /* 12 */ &mrg_by_name_00, + /* 13 */ &mrg_by_name_01, + /* 14 */ &mrg_by_name_02, +}; + +AST_TEST_DEFINE(sdp_update_topology) +{ + enum ast_test_result_state res; + unsigned int idx; + int status; + struct ast_sdp_options *options; + struct ast_stream_topology *topology; + struct ast_sdp_state *test_state = NULL; + + static const struct sdp_format sdp_formats[] = { + { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,g723" }, + { AST_MEDIA_TYPE_VIDEO, "h261,h263,h264,vp8" }, + { AST_MEDIA_TYPE_IMAGE, "t38" }, + }; + + switch(cmd) { + case TEST_INIT: + info->name = "sdp_update_topology"; + info->category = "/main/sdp/"; + info->summary = "Merge topology updates from the system"; + info->description = + "1) Create a SDP state with an optional initial topology.\n" + "2) Update the initial topology with one or two new topologies.\n" + "3) Get the SDP offer to merge the updates into the initial topology.\n" + "4) Check that the offered topology matches the expected topology.\n" + "5) Repeat these steps for each test case defined."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + res = AST_TEST_FAIL; + for (idx = 0; idx < ARRAY_LEN(sdp_update_cases); ++idx) { + ast_test_status_update(test, "Starting update case %d\n", idx); + + /* Create a SDP state with an optional initial topology. */ + options = sdp_options_common(); + if (!options) { + ast_test_status_update(test, "Failed to allocate SDP options\n"); + goto end; + } + if (sdp_update_cases[idx]->max_streams) { + ast_sdp_options_set_max_streams(options, sdp_update_cases[idx]->max_streams); + } + if (build_sdp_option_formats(options, ARRAY_LEN(sdp_formats), sdp_formats)) { + ast_test_status_update(test, "Failed to setup SDP options new stream formats\n"); + goto end; + } + if (sdp_update_cases[idx]->initial) { + topology = build_update_topology(sdp_update_cases[idx]->initial); + if (!topology) { + ast_test_status_update(test, "Failed to build initial SDP state topology\n"); + goto end; + } + } else { + topology = NULL; + } + test_state = ast_sdp_state_alloc(topology, options); + ast_stream_topology_free(topology); + if (!test_state) { + ast_test_status_update(test, "Failed to build SDP state\n"); + goto end; + } + + /* Update the initial topology with one or two new topologies. */ + topology = build_update_topology(sdp_update_cases[idx]->update_1); + if (!topology) { + ast_test_status_update(test, "Failed to build first update SDP state topology\n"); + goto end; + } + status = ast_sdp_state_update_local_topology(test_state, topology); + ast_stream_topology_free(topology); + if (status) { + ast_test_status_update(test, "Failed to update first update SDP state topology\n"); + goto end; + } + if (sdp_update_cases[idx]->update_2) { + topology = build_update_topology(sdp_update_cases[idx]->update_2); + if (!topology) { + ast_test_status_update(test, "Failed to build second update SDP state topology\n"); + goto end; + } + status = ast_sdp_state_update_local_topology(test_state, topology); + ast_stream_topology_free(topology); + if (status) { + ast_test_status_update(test, "Failed to update second update SDP state topology\n"); + goto end; + } + } + + /* Get the SDP offer to merge the updates into the initial topology. */ + if (!ast_sdp_state_get_local_sdp(test_state)) { + ast_test_status_update(test, "Failed to create offer SDP\n"); + goto end; + } + + /* Check that the offered topology matches the expected topology. */ + topology = build_update_topology(sdp_update_cases[idx]->expected); + if (!topology) { + ast_test_status_update(test, "Failed to build expected topology\n"); + goto end; + } + status = cmp_update_topology(test, topology, + ast_sdp_state_get_local_topology(test_state)); + ast_stream_topology_free(topology); + if (status) { + ast_test_status_update(test, "Failed to match expected topology\n"); + goto end; + } + + /* Repeat for each test case defined. */ + ast_sdp_state_free(test_state); + test_state = NULL; + } + res = AST_TEST_PASS; + +end: + ast_sdp_state_free(test_state); + return res; +} + static int unload_module(void) { AST_TEST_UNREGISTER(invalid_rtpmap); @@ -1186,10 +2073,13 @@ static int unload_module(void) AST_TEST_UNREGISTER(find_attr); AST_TEST_UNREGISTER(topology_to_sdp); AST_TEST_UNREGISTER(sdp_to_topology); - AST_TEST_UNREGISTER(sdp_merge_symmetric); - AST_TEST_UNREGISTER(sdp_merge_crisscross); - AST_TEST_UNREGISTER(sdp_merge_asymmetric); + AST_TEST_UNREGISTER(sdp_negotiation_initial); + AST_TEST_UNREGISTER(sdp_negotiation_type_change); + AST_TEST_UNREGISTER(sdp_negotiation_decline_incompatible); + AST_TEST_UNREGISTER(sdp_negotiation_decline_max_streams); + AST_TEST_UNREGISTER(sdp_negotiation_not_acceptable); AST_TEST_UNREGISTER(sdp_ssrc_attributes); + AST_TEST_UNREGISTER(sdp_update_topology); return 0; } @@ -1201,10 +2091,13 @@ static int load_module(void) AST_TEST_REGISTER(find_attr); AST_TEST_REGISTER(topology_to_sdp); AST_TEST_REGISTER(sdp_to_topology); - AST_TEST_REGISTER(sdp_merge_symmetric); - AST_TEST_REGISTER(sdp_merge_crisscross); - AST_TEST_REGISTER(sdp_merge_asymmetric); + AST_TEST_REGISTER(sdp_negotiation_initial); + AST_TEST_REGISTER(sdp_negotiation_type_change); + AST_TEST_REGISTER(sdp_negotiation_decline_incompatible); + AST_TEST_REGISTER(sdp_negotiation_decline_max_streams); + AST_TEST_REGISTER(sdp_negotiation_not_acceptable); AST_TEST_REGISTER(sdp_ssrc_attributes); + AST_TEST_REGISTER(sdp_update_topology); return AST_MODULE_LOAD_SUCCESS; } diff --git a/third-party/pjproject/patches/config_site.h b/third-party/pjproject/patches/config_site.h index a345734b0..561b3a231 100644 --- a/third-party/pjproject/patches/config_site.h +++ b/third-party/pjproject/patches/config_site.h @@ -68,7 +68,7 @@ Enabling it will result in SEGFAULTS when URIs containing escape sequences are encountered. */ #undef PJSIP_UNESCAPE_IN_PLACE -#define PJSIP_MAX_PKT_LEN 6000 +#define PJSIP_MAX_PKT_LEN 32000 #undef PJ_TODO #define PJ_TODO(x) |