/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2017, Digium, Inc. * * George Joseph * * 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/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); } void ast_sdp_free(struct ast_sdp *sdp) { if (!sdp) { return; } 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); ast_free(sdp); } #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 = ast_calloc(1, sizeof *new_sdp); 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); return NULL; } if (AST_VECTOR_INIT(new_sdp->a_lines, 20)) { ast_sdp_free(new_sdp); return NULL; } new_sdp->m_lines = ast_calloc(1, sizeof(*new_sdp->m_lines)); if (!new_sdp->m_lines) { ast_sdp_free(new_sdp); return NULL; } if (AST_VECTOR_INIT(new_sdp->m_lines, 20)) { ast_sdp_free(new_sdp); 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); char *tmp; ast_format_generate_sdp_fmtp(format, rtp_code, &fmtp0); if (ast_str_strlen(fmtp0) == 0) { return -1; } 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 */ tmp = strchr(ast_str_buffer(fmtp0), ':'); if (tmp && tmp[1] != '\0') { tmp++; } else { tmp = ast_str_buffer(fmtp0); } ast_sdp_m_add_a(m_line, ast_sdp_a_alloc("fmtp", tmp)); 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) { sdp_m_add_rtpmap(m_line, options, rtp_code, asterisk_format, format, code); sdp_m_add_fmtp(m_line, format, rtp_code); return 0; } int ast_sdp_add_m_from_rtp_stream(struct ast_sdp *sdp, const struct ast_sdp_state *sdp_state, const struct ast_sdp_options *options, int stream_index) { struct ast_stream *stream = ast_stream_topology_get_stream(ast_sdp_state_get_local_topology(sdp_state), stream_index); struct ast_sdp_m_line *m_line; struct ast_format_cap *caps; int i; int rtp_code; int min_packet_size = 0; int max_packet_size = 0; enum ast_media_type media_type; char tmp[64]; struct ast_sockaddr address_rtp; struct ast_rtp_instance *rtp = ast_sdp_state_get_rtp_instance(sdp_state, stream_index); struct ast_sdp_a_line *a_line; ast_assert(sdp && options && stream); media_type = ast_stream_get_type(stream); if (ast_sdp_state_get_stream_connection_address(sdp_state, 0, &address_rtp)) { return -1; } m_line = ast_sdp_m_alloc( ast_codec_media_type2str(ast_stream_get_type(stream)), ast_sockaddr_port(&address_rtp), 1, options->encryption != AST_SDP_ENCRYPTION_DISABLED ? "RTP/SAVP" : "RTP/AVP", NULL); if (!m_line) { return -1; } caps = ast_stream_get_formats(stream); for (i = 0; i < ast_format_cap_count(caps); i++) { struct ast_format *format = ast_format_cap_get_format(caps, i); if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(rtp), 1, format, 0)) == -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, 0, 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); } ao2_ref(format, -1); } if (media_type != AST_MEDIA_TYPE_VIDEO) { for (i = 1LL; i <= AST_RTP_MAX; i <<= 1) { if (!(options->telephone_event & i)) { continue; } rtp_code = ast_rtp_codecs_payload_code( ast_rtp_instance_get_codecs(rtp), 0, NULL, i); if (rtp_code == -1) { continue; } if (sdp_m_add_rtpmap(m_line, options, rtp_code, 0, NULL, i)) { continue; } if (i == AST_RTP_DTMF) { 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_get_a_count(m_line) == 0) { return 0; } /* 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; } } 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; } } a_line = ast_sdp_a_alloc(ast_sdp_state_get_locally_held(sdp_state, stream_index) ? "sendonly" : "sendrecv", ""); 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_add_m(sdp, m_line)) { ast_sdp_m_free(m_line); return -1; } return 0; } struct ast_sdp *ast_sdp_create_from_state(const struct ast_sdp_state *sdp_state) { const struct ast_sdp_options *options; RAII_VAR(struct ast_sdp *, sdp, NULL, ao2_cleanup); const const struct ast_stream_topology *topology; int stream_count; int stream_num; struct ast_sdp_o_line *o_line = NULL; struct ast_sdp_c_line *c_line = NULL; struct ast_sdp_s_line *s_line = NULL; struct ast_sdp_t_line *t_line = NULL; char *address_type; struct timeval tv = ast_tvnow(); uint32_t t; ast_assert(!!sdp_state); options = ast_sdp_state_get_options(sdp_state); topology = ast_sdp_state_get_local_topology(sdp_state); stream_count = ast_stream_topology_get_count(topology); t = tv.tv_sec + 2208988800UL; address_type = (strchr(options->media_address, ':') ? "IP6" : "IP4"); o_line = ast_sdp_o_alloc(options->sdpowner, t, t, address_type, options->media_address); if (!o_line) { goto error; } c_line = ast_sdp_c_alloc(address_type, options->media_address); if (!c_line) { goto error; } s_line = ast_sdp_s_alloc(options->sdpsession); if (!s_line) { goto error; } sdp = ast_sdp_alloc(o_line, c_line, s_line, NULL); if (!sdp) { goto error; } for (stream_num = 0; stream_num < stream_count; stream_num++) { enum ast_media_type type = ast_stream_get_type(ast_stream_topology_get_stream(topology, stream_num)); if (type == AST_MEDIA_TYPE_AUDIO || type == AST_MEDIA_TYPE_VIDEO) { if (ast_sdp_add_m_from_rtp_stream(sdp, sdp_state, options, stream_num)) { goto error; } } } return sdp; error: if (sdp) { ast_sdp_free(sdp); } else { ast_sdp_t_free(t_line); ast_sdp_s_free(s_line); ast_sdp_c_free(c_line); ast_sdp_o_free(o_line); } return NULL; }