The SDP offer/answer model requires an answer to an offer before a new SDP can be processed. This allows our local SDP creation to be deferred until we know that we need to create an offer or an answer SDP. Once the local SDP is created it won't change until the SDP negotiation is restarted. An offer SDP in an initial SIP INVITE can receive more than one answer SDP. In this case, we need to merge each answer SDP with our original offer capabilities to get the currently negotiated capabilities. To satisfy this requirement means that we cannot update our proposed capabilities until the negotiations are restarted. Local topology updates from ast_sdp_state_update_local_topology() are merged together until the next offer SDP is created. These accumulated updates are then merged with the current negotiated capabilities to create the new proposed capabilities that the offer SDP is built. Local topology updates are merged in several passes to attempt to be smart about how streams from the system are matched with the previously negotiated stream slots. To allow for T.38 support when merging, type matching considers audio and image types to be equivalent. First streams are matched by stream name and type. Then streams are matched by stream type only. Any remaining unmatched existing streams are declined. Any new active streams are either backfilled into pre-merge declined slots or appended onto the end of the merged topology. Any excess new streams above the maximum supported number of streams are simply discarded. Remote topology negotiation merges depend if the topology is an offer or answer. An offer remote topology negotiation dictates the stream slot ordering and new streams can be added. A remote offer can do anything to the previously negotiated streams except reduce the number of stream slots. An answer remote topology negotiation is limited to what our offer requested. The answer can only decline streams, pick codecs from the offered list, or indicate the remote's stream hold state. I had originally kept the RTP instance if the remote offer SDP changed a stream type between audio and video since they both use RTP. However, I later removed this support in favor of simply creating a new RTP instance since the stream's purpose has to be changing anyway. Any RTP packets from the old stream type might cause mischief for the bridged peer. * Added ast_sdp_state_restart_negotiations() to restart the SDP offer/answer negotiations. We will thus know to create a new local SDP when it is time to create an offer or answer. * Removed ast_sdp_state_reset(). Save the current topology before starting T.38. To recover from T.38 simply update the local topology to the saved topology and restart the SDP negotiations to get the offer SDP renegotiating the previous configuration. * Allow initial topology for ast_sdp_state_alloc() to be NULL so an initial remote offer SDP can dictate the streams we start with. We can always update the local topology later if it turns out we need to offer SDP first because the remote chose to defer sending us a SDP. * Made the ast_sdp_state_alloc() initial topology limit to max_streams, limit to configured codecs, handle declined streams, and discard unsupported types. * Convert struct ast_sdp to ao2 object. Needed to easily save off a remote SDP to refer to later for various reasons such as generating declined m= lines in the local SDP. * Improve converting remote SDP streams to a topology including stream state. A stream state of AST_STREAM_STATE_REMOVED indicates the stream is declined/dead. * Improve merging streams to take into account the stream state. * Added query for remote hold state. * Added maximum streams allowed SDP config option. * Added ability to create new streams as needed. New streams are created with configured default audio, video, or image codecs depending on stream type. * Added global locally_held state along with a per stream local hold state. Historically, Asterisk only has a global locally held state because when the we put the remote on hold we do it for all active streams. * Added queries for a rejected offer and current SDP negotiation role. The rejected query allows the using module to know how to respond to a failed remote SDP set. Should the using module respond with a 488 Not Acceptable Here or 500 Internal Error to the offer SDP? * Moved sdp_state_capabilities.connection_address to ast_sdp_state. There seems no reason to keep it in the sdp_state_capabilities struct since it was only used by the ast_sdp_state.proposed_capabilities instance. * Callbacks are now available to allow the using module some customization of negotiated streams and to complete setting up streams for use. See the typedef doxygen for each callback for what is allowable and when they are called. * Added topology answerer modify callback. * Added topology pre and post apply callbacks. * Added topology offerer modify callback. * Added topology offerer configure callback. * Had to rework the unit tests because I changed how SDP topologies are merged. Replaced several unit tests with new negotiation tests. Change-Id: If07fe6d79fbdce33968a9401d41d908385043a06
951 lines
23 KiB
C
951 lines
23 KiB
C
/*
|
|
* Asterisk -- An open source telephony toolkit.
|
|
*
|
|
* Copyright (C) 2017, Digium, Inc.
|
|
*
|
|
* George Joseph <gjoseph@digium.com>
|
|
*
|
|
* See http://www.asterisk.org for more information about
|
|
* the Asterisk project. Please do not directly contact
|
|
* any of the maintainers of this project for assistance;
|
|
* the project provides a web site, mailing lists and IRC
|
|
* channels for your use.
|
|
*
|
|
* This program is free software, distributed under the terms of
|
|
* the GNU General Public License Version 2. See the LICENSE file
|
|
* at the top of the source tree.
|
|
*/
|
|
|
|
|
|
#include "asterisk.h"
|
|
#include "asterisk/utils.h"
|
|
#include "asterisk/netsock2.h"
|
|
#include "asterisk/codec.h"
|
|
#include "asterisk/format.h"
|
|
#include "asterisk/format_cap.h"
|
|
#include "asterisk/format_cache.h"
|
|
#include "asterisk/rtp_engine.h"
|
|
#include "asterisk/sdp_state.h"
|
|
#include "asterisk/sdp_options.h"
|
|
#include "asterisk/sdp_translator.h"
|
|
#include "asterisk/sdp.h"
|
|
#include "asterisk/vector.h"
|
|
#include "asterisk/utils.h"
|
|
#include "asterisk/stream.h"
|
|
#include "sdp_private.h"
|
|
|
|
void ast_sdp_a_free(struct ast_sdp_a_line *a_line)
|
|
{
|
|
ast_free(a_line);
|
|
}
|
|
|
|
void ast_sdp_a_lines_free(struct ast_sdp_a_lines *a_lines)
|
|
{
|
|
if (!a_lines) {
|
|
return;
|
|
}
|
|
|
|
AST_VECTOR_CALLBACK_VOID(a_lines, ast_sdp_a_free);
|
|
AST_VECTOR_FREE(a_lines);
|
|
ast_free(a_lines);
|
|
}
|
|
|
|
void ast_sdp_c_free(struct ast_sdp_c_line *c_line)
|
|
{
|
|
ast_free(c_line);
|
|
}
|
|
|
|
void ast_sdp_payload_free(struct ast_sdp_payload *payload)
|
|
{
|
|
ast_free(payload);
|
|
}
|
|
|
|
void ast_sdp_payloads_free(struct ast_sdp_payloads *payloads)
|
|
{
|
|
if (!payloads) {
|
|
return;
|
|
}
|
|
|
|
AST_VECTOR_CALLBACK_VOID(payloads, ast_sdp_payload_free);
|
|
AST_VECTOR_FREE(payloads);
|
|
ast_free(payloads);
|
|
}
|
|
|
|
void ast_sdp_m_free(struct ast_sdp_m_line *m_line)
|
|
{
|
|
if (!m_line) {
|
|
return;
|
|
}
|
|
|
|
ast_sdp_a_lines_free(m_line->a_lines);
|
|
ast_sdp_payloads_free(m_line->payloads);
|
|
ast_sdp_c_free(m_line->c_line);
|
|
ast_free(m_line);
|
|
}
|
|
|
|
void ast_sdp_m_lines_free(struct ast_sdp_m_lines *m_lines)
|
|
{
|
|
if (!m_lines) {
|
|
return;
|
|
}
|
|
|
|
AST_VECTOR_CALLBACK_VOID(m_lines, ast_sdp_m_free);
|
|
AST_VECTOR_FREE(m_lines);
|
|
ast_free(m_lines);
|
|
}
|
|
|
|
void ast_sdp_o_free(struct ast_sdp_o_line *o_line)
|
|
{
|
|
ast_free(o_line);
|
|
}
|
|
|
|
void ast_sdp_s_free(struct ast_sdp_s_line *s_line)
|
|
{
|
|
ast_free(s_line);
|
|
}
|
|
|
|
void ast_sdp_t_free(struct ast_sdp_t_line *t_line)
|
|
{
|
|
ast_free(t_line);
|
|
}
|
|
|
|
static void ast_sdp_dtor(void *vdoomed)
|
|
{
|
|
struct ast_sdp *sdp = vdoomed;
|
|
|
|
ast_sdp_o_free(sdp->o_line);
|
|
ast_sdp_s_free(sdp->s_line);
|
|
ast_sdp_c_free(sdp->c_line);
|
|
ast_sdp_t_free(sdp->t_line);
|
|
ast_sdp_a_lines_free(sdp->a_lines);
|
|
ast_sdp_m_lines_free(sdp->m_lines);
|
|
}
|
|
|
|
#define COPY_STR_AND_ADVANCE(p, dest, source) \
|
|
({ \
|
|
dest = p; \
|
|
strcpy(dest, source); \
|
|
p += (strlen(source) + 1); \
|
|
})
|
|
|
|
struct ast_sdp_a_line *ast_sdp_a_alloc(const char *name, const char *value)
|
|
{
|
|
struct ast_sdp_a_line *a_line;
|
|
size_t len;
|
|
char *p;
|
|
|
|
ast_assert(!ast_strlen_zero(name));
|
|
|
|
if (ast_strlen_zero(value)) {
|
|
value = "";
|
|
}
|
|
|
|
len = sizeof(*a_line) + strlen(name) + strlen(value) + 2;
|
|
a_line = ast_calloc(1, len);
|
|
if (!a_line) {
|
|
return NULL;
|
|
}
|
|
|
|
p = ((char *)a_line) + sizeof(*a_line);
|
|
|
|
COPY_STR_AND_ADVANCE(p, a_line->name, name);
|
|
COPY_STR_AND_ADVANCE(p, a_line->value, value);
|
|
|
|
return a_line;
|
|
}
|
|
|
|
struct ast_sdp_c_line *ast_sdp_c_alloc(const char *address_type, const char *address)
|
|
{
|
|
struct ast_sdp_c_line *c_line;
|
|
size_t len;
|
|
char *p;
|
|
|
|
ast_assert(!ast_strlen_zero(address_type) && !ast_strlen_zero(address));
|
|
|
|
len = sizeof(*c_line) + strlen(address_type) + strlen(address) + 2;
|
|
c_line = ast_calloc(1, len);
|
|
if (!c_line) {
|
|
return NULL;
|
|
}
|
|
|
|
p = ((char *)c_line) + sizeof(*c_line);
|
|
|
|
COPY_STR_AND_ADVANCE(p, c_line->address_type, address_type);
|
|
COPY_STR_AND_ADVANCE(p, c_line->address, address);
|
|
|
|
return c_line;
|
|
}
|
|
|
|
struct ast_sdp_payload *ast_sdp_payload_alloc(const char *fmt)
|
|
{
|
|
struct ast_sdp_payload *payload;
|
|
size_t len;
|
|
|
|
ast_assert(!ast_strlen_zero(fmt));
|
|
|
|
len = sizeof(*payload) + strlen(fmt) + 1;
|
|
payload = ast_calloc(1, len);
|
|
if (!payload) {
|
|
return NULL;
|
|
}
|
|
|
|
payload->fmt = ((char *)payload) + sizeof(*payload);
|
|
strcpy(payload->fmt, fmt); /* Safe */
|
|
|
|
return payload;
|
|
}
|
|
|
|
struct ast_sdp_m_line *ast_sdp_m_alloc(const char *type, uint16_t port,
|
|
uint16_t port_count, const char *proto, struct ast_sdp_c_line *c_line)
|
|
{
|
|
struct ast_sdp_m_line *m_line;
|
|
size_t len;
|
|
char *p;
|
|
|
|
ast_assert(!ast_strlen_zero(type) && !ast_strlen_zero(proto));
|
|
|
|
len = sizeof(*m_line) + strlen(type) + strlen(proto) + 2;
|
|
m_line = ast_calloc(1, len);
|
|
if (!m_line) {
|
|
return NULL;
|
|
}
|
|
|
|
m_line->a_lines = ast_calloc(1, sizeof(*m_line->a_lines));
|
|
if (!m_line->a_lines) {
|
|
ast_sdp_m_free(m_line);
|
|
return NULL;
|
|
}
|
|
if (AST_VECTOR_INIT(m_line->a_lines, 20)) {
|
|
ast_sdp_m_free(m_line);
|
|
return NULL;
|
|
}
|
|
|
|
m_line->payloads = ast_calloc(1, sizeof(*m_line->payloads));
|
|
if (!m_line->payloads) {
|
|
ast_sdp_m_free(m_line);
|
|
return NULL;
|
|
}
|
|
if (AST_VECTOR_INIT(m_line->payloads, 20)) {
|
|
ast_sdp_m_free(m_line);
|
|
return NULL;
|
|
}
|
|
|
|
p = ((char *)m_line) + sizeof(*m_line);
|
|
|
|
COPY_STR_AND_ADVANCE(p, m_line->type, type);
|
|
COPY_STR_AND_ADVANCE(p, m_line->proto, proto);
|
|
m_line->port = port;
|
|
m_line->port_count = port_count;
|
|
m_line->c_line = c_line;
|
|
|
|
return m_line;
|
|
}
|
|
|
|
struct ast_sdp_s_line *ast_sdp_s_alloc(const char *session_name)
|
|
{
|
|
struct ast_sdp_s_line *s_line;
|
|
size_t len;
|
|
|
|
if (ast_strlen_zero(session_name)) {
|
|
session_name = " ";
|
|
}
|
|
|
|
len = sizeof(*s_line) + strlen(session_name) + 1;
|
|
s_line = ast_calloc(1, len);
|
|
if (!s_line) {
|
|
return NULL;
|
|
}
|
|
|
|
s_line->session_name = ((char *)s_line) + sizeof(*s_line);
|
|
strcpy(s_line->session_name, session_name); /* Safe */
|
|
|
|
return s_line;
|
|
}
|
|
|
|
struct ast_sdp_t_line *ast_sdp_t_alloc(uint64_t start_time, uint64_t stop_time)
|
|
{
|
|
struct ast_sdp_t_line *t_line;
|
|
|
|
t_line = ast_calloc(1, sizeof(*t_line));
|
|
if (!t_line) {
|
|
return NULL;
|
|
}
|
|
|
|
t_line->start_time = start_time;
|
|
t_line->stop_time = stop_time;
|
|
|
|
return t_line;
|
|
}
|
|
|
|
struct ast_sdp_o_line *ast_sdp_o_alloc(const char *username, uint64_t session_id,
|
|
uint64_t session_version, const char *address_type, const char *address)
|
|
{
|
|
struct ast_sdp_o_line *o_line;
|
|
size_t len;
|
|
char *p;
|
|
|
|
ast_assert(!ast_strlen_zero(username) && !ast_strlen_zero(address_type)
|
|
&& !ast_strlen_zero(address));
|
|
|
|
len = sizeof(*o_line) + strlen(username) + strlen(address_type) + strlen(address) + 3;
|
|
o_line = ast_calloc(1, len);
|
|
if (!o_line) {
|
|
return NULL;
|
|
}
|
|
|
|
o_line->session_id = session_id;
|
|
o_line->session_version = session_version;
|
|
|
|
p = ((char *)o_line) + sizeof(*o_line);
|
|
|
|
COPY_STR_AND_ADVANCE(p, o_line->username, username);
|
|
COPY_STR_AND_ADVANCE(p, o_line->address_type, address_type);
|
|
COPY_STR_AND_ADVANCE(p, o_line->address, address);
|
|
|
|
return o_line;
|
|
}
|
|
|
|
struct ast_sdp *ast_sdp_alloc(struct ast_sdp_o_line *o_line,
|
|
struct ast_sdp_c_line *c_line, struct ast_sdp_s_line *s_line,
|
|
struct ast_sdp_t_line *t_line)
|
|
{
|
|
struct ast_sdp *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) {
|
|
ao2_ref(new_sdp, -1);
|
|
return NULL;
|
|
}
|
|
if (AST_VECTOR_INIT(new_sdp->a_lines, 20)) {
|
|
ao2_ref(new_sdp, -1);
|
|
return NULL;
|
|
}
|
|
|
|
new_sdp->m_lines = ast_calloc(1, sizeof(*new_sdp->m_lines));
|
|
if (!new_sdp->m_lines) {
|
|
ao2_ref(new_sdp, -1);
|
|
return NULL;
|
|
}
|
|
if (AST_VECTOR_INIT(new_sdp->m_lines, 20)) {
|
|
ao2_ref(new_sdp, -1);
|
|
return NULL;
|
|
}
|
|
|
|
new_sdp->o_line = o_line;
|
|
new_sdp->c_line = c_line;
|
|
new_sdp->s_line = s_line;
|
|
new_sdp->t_line = t_line;
|
|
|
|
return new_sdp;
|
|
}
|
|
|
|
int ast_sdp_add_a(struct ast_sdp *sdp, struct ast_sdp_a_line *a_line)
|
|
{
|
|
ast_assert(sdp && a_line);
|
|
|
|
return AST_VECTOR_APPEND(sdp->a_lines, a_line);
|
|
}
|
|
|
|
int ast_sdp_get_a_count(const struct ast_sdp *sdp)
|
|
{
|
|
ast_assert(sdp != NULL);
|
|
|
|
return AST_VECTOR_SIZE(sdp->a_lines);
|
|
}
|
|
|
|
struct ast_sdp_a_line *ast_sdp_get_a(const struct ast_sdp *sdp, int index)
|
|
{
|
|
ast_assert(sdp != NULL);
|
|
|
|
return AST_VECTOR_GET(sdp->a_lines, index);
|
|
}
|
|
|
|
int ast_sdp_add_m(struct ast_sdp *sdp, struct ast_sdp_m_line *m_line)
|
|
{
|
|
ast_assert(sdp && m_line);
|
|
|
|
return AST_VECTOR_APPEND(sdp->m_lines, m_line);
|
|
}
|
|
|
|
int ast_sdp_get_m_count(const struct ast_sdp *sdp)
|
|
{
|
|
ast_assert(sdp != NULL);
|
|
|
|
return AST_VECTOR_SIZE(sdp->m_lines);
|
|
}
|
|
|
|
struct ast_sdp_m_line *ast_sdp_get_m(const struct ast_sdp *sdp, int index)
|
|
{
|
|
ast_assert(sdp != NULL);
|
|
|
|
return AST_VECTOR_GET(sdp->m_lines, index);
|
|
}
|
|
|
|
int ast_sdp_m_add_a(struct ast_sdp_m_line *m_line, struct ast_sdp_a_line *a_line)
|
|
{
|
|
ast_assert(m_line && a_line);
|
|
|
|
return AST_VECTOR_APPEND(m_line->a_lines, a_line);
|
|
}
|
|
|
|
int ast_sdp_m_get_a_count(const struct ast_sdp_m_line *m_line)
|
|
{
|
|
ast_assert(m_line != NULL);
|
|
|
|
return AST_VECTOR_SIZE(m_line->a_lines);
|
|
}
|
|
|
|
struct ast_sdp_a_line *ast_sdp_m_get_a(const struct ast_sdp_m_line *m_line, int index)
|
|
{
|
|
ast_assert(m_line != NULL);
|
|
|
|
return AST_VECTOR_GET(m_line->a_lines, index);
|
|
}
|
|
|
|
int ast_sdp_m_add_payload(struct ast_sdp_m_line *m_line, struct ast_sdp_payload *payload)
|
|
{
|
|
ast_assert(m_line && payload);
|
|
|
|
return AST_VECTOR_APPEND(m_line->payloads, payload);
|
|
}
|
|
|
|
int ast_sdp_m_get_payload_count(const struct ast_sdp_m_line *m_line)
|
|
{
|
|
ast_assert(m_line != NULL);
|
|
|
|
return AST_VECTOR_SIZE(m_line->payloads);
|
|
}
|
|
|
|
struct ast_sdp_payload *ast_sdp_m_get_payload(const struct ast_sdp_m_line *m_line, int index)
|
|
{
|
|
ast_assert(m_line != NULL);
|
|
|
|
return AST_VECTOR_GET(m_line->payloads, index);
|
|
}
|
|
|
|
static int sdp_m_add_fmtp(struct ast_sdp_m_line *m_line, const struct ast_format *format,
|
|
int rtp_code)
|
|
{
|
|
struct ast_str *fmtp0 = ast_str_alloca(256);
|
|
struct ast_sdp_a_line *a_line;
|
|
char *tmp;
|
|
|
|
ast_format_generate_sdp_fmtp(format, rtp_code, &fmtp0);
|
|
if (ast_str_strlen(fmtp0) == 0) {
|
|
/* Format doesn't have fmtp attributes */
|
|
return 0;
|
|
}
|
|
|
|
tmp = ast_str_buffer(fmtp0) + ast_str_strlen(fmtp0) - 1;
|
|
/* remove any carriage return line feeds */
|
|
while (*tmp == '\r' || *tmp == '\n') --tmp;
|
|
*++tmp = '\0';
|
|
|
|
/*
|
|
* ast...generate gives us everything, just need value
|
|
*
|
|
* It can also give multiple fmtp attribute lines. (silk does)
|
|
*/
|
|
tmp = strchr(ast_str_buffer(fmtp0), ':');
|
|
if (tmp && tmp[1] != '\0') {
|
|
tmp++;
|
|
} else {
|
|
tmp = ast_str_buffer(fmtp0);
|
|
}
|
|
|
|
a_line = ast_sdp_a_alloc("fmtp", tmp);
|
|
if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sdp_m_add_rtpmap(struct ast_sdp_m_line *m_line,
|
|
const struct ast_sdp_options *options, int rtp_code, int asterisk_format,
|
|
const struct ast_format *format, int code)
|
|
{
|
|
char tmp[64];
|
|
const char *enc_name;
|
|
struct ast_sdp_payload *payload;
|
|
struct ast_sdp_a_line *a_line;
|
|
|
|
snprintf(tmp, sizeof(tmp), "%d", rtp_code);
|
|
payload = ast_sdp_payload_alloc(tmp);
|
|
if (!payload || ast_sdp_m_add_payload(m_line, payload)) {
|
|
ast_sdp_payload_free(payload);
|
|
return -1;
|
|
}
|
|
|
|
enc_name = ast_rtp_lookup_mime_subtype2(asterisk_format, format, code,
|
|
options->g726_non_standard ? AST_RTP_OPT_G726_NONSTANDARD : 0);
|
|
|
|
snprintf(tmp, sizeof(tmp), "%d %s/%d%s%s", rtp_code, enc_name,
|
|
ast_rtp_lookup_sample_rate2(asterisk_format, format, code),
|
|
strcmp(enc_name, "opus") ? "" : "/", strcmp(enc_name, "opus") ? "" : "2");
|
|
|
|
a_line = ast_sdp_a_alloc("rtpmap", tmp);
|
|
if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
|
|
ast_sdp_a_free(a_line);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ast_sdp_m_add_format(struct ast_sdp_m_line *m_line, const struct ast_sdp_options *options,
|
|
int rtp_code, int asterisk_format, const struct ast_format *format, int code)
|
|
{
|
|
return sdp_m_add_rtpmap(m_line, options, rtp_code, asterisk_format, format, code)
|
|
|| sdp_m_add_fmtp(m_line, format, rtp_code) ? -1 : 0;
|
|
}
|
|
|
|
static int sdp_find_a_common(const struct ast_sdp_a_lines *a_lines, int start,
|
|
const char *attr_name, int payload)
|
|
{
|
|
struct ast_sdp_a_line *a_line;
|
|
int idx;
|
|
|
|
ast_assert(-1 <= start);
|
|
|
|
for (idx = start + 1; idx < AST_VECTOR_SIZE(a_lines); ++idx) {
|
|
int a_line_payload;
|
|
|
|
a_line = AST_VECTOR_GET(a_lines, idx);
|
|
if (strcmp(a_line->name, attr_name)) {
|
|
continue;
|
|
}
|
|
|
|
if (payload >= 0) {
|
|
int sscanf_res;
|
|
|
|
sscanf_res = sscanf(a_line->value, "%30d", &a_line_payload);
|
|
if (sscanf_res == 1 && payload == a_line_payload) {
|
|
return idx;
|
|
}
|
|
} else {
|
|
return idx;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int ast_sdp_find_a_first(const struct ast_sdp *sdp, const char *attr_name, int payload)
|
|
{
|
|
return sdp_find_a_common(sdp->a_lines, -1, attr_name, payload);
|
|
}
|
|
|
|
int ast_sdp_find_a_next(const struct ast_sdp *sdp, int last, const char *attr_name, int payload)
|
|
{
|
|
return sdp_find_a_common(sdp->a_lines, last, attr_name, payload);
|
|
}
|
|
|
|
struct ast_sdp_a_line *ast_sdp_find_attribute(const struct ast_sdp *sdp,
|
|
const char *attr_name, int payload)
|
|
{
|
|
int idx;
|
|
|
|
idx = ast_sdp_find_a_first(sdp, attr_name, payload);
|
|
if (idx < 0) {
|
|
return NULL;
|
|
}
|
|
return ast_sdp_get_a(sdp, idx);
|
|
}
|
|
|
|
int ast_sdp_m_find_a_first(const struct ast_sdp_m_line *m_line, const char *attr_name,
|
|
int payload)
|
|
{
|
|
return sdp_find_a_common(m_line->a_lines, -1, attr_name, payload);
|
|
}
|
|
|
|
int ast_sdp_m_find_a_next(const struct ast_sdp_m_line *m_line, int last,
|
|
const char *attr_name, int payload)
|
|
{
|
|
return sdp_find_a_common(m_line->a_lines, last, attr_name, payload);
|
|
}
|
|
|
|
struct ast_sdp_a_line *ast_sdp_m_find_attribute(const struct ast_sdp_m_line *m_line,
|
|
const char *attr_name, int payload)
|
|
{
|
|
int idx;
|
|
|
|
idx = ast_sdp_m_find_a_first(m_line, attr_name, payload);
|
|
if (idx < 0) {
|
|
return NULL;
|
|
}
|
|
return ast_sdp_m_get_a(m_line, idx);
|
|
}
|
|
|
|
struct ast_sdp_rtpmap *ast_sdp_rtpmap_alloc(int payload, const char *encoding_name,
|
|
int clock_rate, const char *encoding_parameters)
|
|
{
|
|
struct ast_sdp_rtpmap *rtpmap;
|
|
char *buf_pos;
|
|
|
|
rtpmap = ast_calloc(1, sizeof(*rtpmap) + strlen(encoding_name) + strlen(encoding_parameters) + 2);
|
|
if (!rtpmap) {
|
|
return NULL;
|
|
}
|
|
|
|
rtpmap->payload = payload;
|
|
rtpmap->clock_rate = clock_rate;
|
|
|
|
buf_pos = rtpmap->buf;
|
|
COPY_STR_AND_ADVANCE(buf_pos, rtpmap->encoding_name, encoding_name);
|
|
COPY_STR_AND_ADVANCE(buf_pos, rtpmap->encoding_parameters, encoding_parameters);
|
|
|
|
return rtpmap;
|
|
}
|
|
|
|
void ast_sdp_rtpmap_free(struct ast_sdp_rtpmap *rtpmap)
|
|
{
|
|
ast_free(rtpmap);
|
|
}
|
|
|
|
struct ast_sdp_rtpmap *ast_sdp_a_get_rtpmap(const struct ast_sdp_a_line *a_line)
|
|
{
|
|
char *value_copy;
|
|
char *slash;
|
|
int payload;
|
|
char encoding_name[64];
|
|
int clock_rate;
|
|
char *encoding_parameters;
|
|
struct ast_sdp_rtpmap *rtpmap;
|
|
int clock_rate_len;
|
|
|
|
value_copy = ast_strip(ast_strdupa(a_line->value));
|
|
|
|
if (sscanf(value_copy, "%30d %63s", &payload, encoding_name) != 2) {
|
|
return NULL;
|
|
}
|
|
|
|
slash = strchr(encoding_name, '/');
|
|
if (!slash) {
|
|
return NULL;
|
|
}
|
|
*slash++ = '\0';
|
|
if (ast_strlen_zero(encoding_name)) {
|
|
return NULL;
|
|
}
|
|
if (sscanf(slash, "%30d%n", &clock_rate, &clock_rate_len) < 1) {
|
|
return NULL;
|
|
}
|
|
|
|
slash += clock_rate_len;
|
|
if (!ast_strlen_zero(slash)) {
|
|
if (*slash == '/') {
|
|
*slash++ = '\0';
|
|
encoding_parameters = slash;
|
|
if (ast_strlen_zero(encoding_parameters)) {
|
|
return NULL;
|
|
}
|
|
} else {
|
|
return NULL;
|
|
}
|
|
} else {
|
|
encoding_parameters = "";
|
|
}
|
|
|
|
rtpmap = ast_sdp_rtpmap_alloc(payload, encoding_name, clock_rate,
|
|
encoding_parameters);
|
|
|
|
return rtpmap;
|
|
}
|
|
|
|
/*!
|
|
* \brief Turn an SDP attribute into an sdp_rtpmap structure
|
|
*
|
|
* \param m_line The media section where this attribute was found.
|
|
* \param payload The RTP payload to find an rtpmap for
|
|
* \param[out] rtpmap The rtpmap to fill in.
|
|
* \return Zero if successful, otherwise less than zero
|
|
*/
|
|
static struct ast_sdp_rtpmap *sdp_payload_get_rtpmap(const struct ast_sdp_m_line *m_line, int payload)
|
|
{
|
|
struct ast_sdp_a_line *rtpmap_attr;
|
|
|
|
rtpmap_attr = ast_sdp_m_find_attribute(m_line, "rtpmap", payload);
|
|
if (!rtpmap_attr) {
|
|
return NULL;
|
|
}
|
|
|
|
return ast_sdp_a_get_rtpmap(rtpmap_attr);
|
|
}
|
|
|
|
static void process_fmtp_value(const char *value, int payload, struct ast_rtp_codecs *codecs)
|
|
{
|
|
char *param;
|
|
char *param_start;
|
|
char *param_end;
|
|
size_t len;
|
|
struct ast_format *replace;
|
|
struct ast_format *format;
|
|
|
|
/*
|
|
* Extract the "a=fmtp:%d %s" attribute parameter string value which
|
|
* starts after the colon.
|
|
*/
|
|
param_start = ast_skip_nonblanks(value);/* Skip payload type */
|
|
param_start = ast_skip_blanks(param_start);
|
|
param_end = ast_skip_nonblanks(param_start);
|
|
if (param_end == param_start) {
|
|
/* There is no parameter string */
|
|
return;
|
|
}
|
|
len = param_end - param_start;
|
|
param = ast_alloca(len + 1);
|
|
memcpy(param, param_start, len);
|
|
param[len] = '\0';
|
|
|
|
format = ast_rtp_codecs_get_payload_format(codecs, payload);
|
|
if (!format) {
|
|
return;
|
|
}
|
|
|
|
replace = ast_format_parse_sdp_fmtp(format, param);
|
|
if (replace) {
|
|
ast_rtp_codecs_payload_replace_format(codecs, payload, replace);
|
|
ao2_ref(replace, -1);
|
|
}
|
|
ao2_ref(format, -1);
|
|
}
|
|
|
|
/*!
|
|
* \brief Find and process all fmtp attribute lines for a given payload
|
|
*
|
|
* \param m_line The stream on which to search for the fmtp attributes
|
|
* \param payload The specific fmtp attribute to search for
|
|
* \param codecs The current RTP codecs that have been built up
|
|
*/
|
|
static void process_fmtp_lines(const struct ast_sdp_m_line *m_line, int payload,
|
|
struct ast_rtp_codecs *codecs)
|
|
{
|
|
const struct ast_sdp_a_line *a_line;
|
|
int idx;
|
|
|
|
idx = ast_sdp_m_find_a_first(m_line, "fmtp", payload);
|
|
for (; 0 <= idx; idx = ast_sdp_m_find_a_next(m_line, idx, "fmtp", payload)) {
|
|
a_line = ast_sdp_m_get_a(m_line, idx);
|
|
ast_assert(a_line != NULL);
|
|
|
|
process_fmtp_value(a_line->value, payload, codecs);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \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.
|
|
*/
|
|
static void rtp_codecs_free(struct ast_rtp_codecs *codecs)
|
|
{
|
|
if (codecs) {
|
|
ast_rtp_codecs_payloads_destroy(codecs);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Convert an SDP stream into an Asterisk stream
|
|
*
|
|
* 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_a_lines *a_lines, const struct ast_sdp_m_line *m_line, int g726_non_standard)
|
|
{
|
|
int i;
|
|
int non_ast_fmts;
|
|
struct ast_rtp_codecs *codecs;
|
|
struct ast_format_cap *caps;
|
|
struct ast_stream *stream;
|
|
enum ast_rtp_options options;
|
|
|
|
caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
|
|
if (!caps) {
|
|
return NULL;
|
|
}
|
|
stream = ast_stream_alloc(m_line->type, ast_media_type_from_str(m_line->type));
|
|
if (!stream) {
|
|
ao2_ref(caps, -1);
|
|
return NULL;
|
|
}
|
|
|
|
switch (ast_stream_get_type(stream)) {
|
|
case AST_MEDIA_TYPE_AUDIO:
|
|
case AST_MEDIA_TYPE_VIDEO:
|
|
codecs = ast_calloc(1, sizeof(*codecs));
|
|
if (!codecs || ast_rtp_codecs_payloads_initialize(codecs)) {
|
|
rtp_codecs_free(codecs);
|
|
ast_stream_free(stream);
|
|
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) {
|
|
struct ast_sdp_payload *payload_s;
|
|
struct ast_sdp_rtpmap *rtpmap;
|
|
int payload;
|
|
|
|
payload_s = ast_sdp_m_get_payload(m_line, i);
|
|
sscanf(payload_s->fmt, "%30d", &payload);
|
|
|
|
rtpmap = sdp_payload_get_rtpmap(m_line, payload);
|
|
if (!rtpmap) {
|
|
/* No rtpmap attribute. Try static payload type format assignment */
|
|
ast_rtp_codecs_payloads_set_m_type(codecs, NULL, payload);
|
|
continue;
|
|
}
|
|
|
|
if (!ast_rtp_codecs_payloads_set_rtpmap_type_rate(codecs, NULL, payload,
|
|
m_line->type, rtpmap->encoding_name, options, rtpmap->clock_rate)) {
|
|
/* Successfully mapped the payload type to format */
|
|
process_fmtp_lines(m_line, payload, codecs);
|
|
}
|
|
ast_sdp_rtpmap_free(rtpmap);
|
|
}
|
|
|
|
ast_rtp_codecs_payload_formats(codecs, caps, &non_ast_fmts);
|
|
|
|
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;
|
|
|
|
/* As we don't carry T.38 over RTP we do our own format check */
|
|
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;
|
|
}
|
|
|
|
ast_stream_set_formats(stream, caps);
|
|
ao2_ref(caps, -1);
|
|
|
|
return stream;
|
|
}
|
|
|
|
struct ast_stream_topology *ast_get_topology_from_sdp(const struct ast_sdp *sdp, int g726_non_standard)
|
|
{
|
|
struct ast_stream_topology *topology;
|
|
int i;
|
|
|
|
topology = ast_stream_topology_alloc();
|
|
if (!topology) {
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < ast_sdp_get_m_count(sdp); ++i) {
|
|
struct ast_stream *stream;
|
|
|
|
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
|
|
* we failed to create a corresponding stream.
|
|
*/
|
|
ast_stream_topology_free(topology);
|
|
return NULL;
|
|
}
|
|
if (ast_stream_topology_append_stream(topology, stream) < 0) {
|
|
/* Failed to add stream to topology */
|
|
ast_stream_free(stream);
|
|
ast_stream_topology_free(topology);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return topology;
|
|
}
|