diff options
41 files changed, 2982 insertions, 518 deletions
diff --git a/addons/format_mp3.c b/addons/format_mp3.c index e0f57b86b..bb0b20850 100644 --- a/addons/format_mp3.c +++ b/addons/format_mp3.c @@ -118,9 +118,11 @@ static int mp3_squeue(struct ast_filestream *s) res = ftell(s->f); p->sbuflen = fread(p->sbuf, 1, MP3_SCACHE, s->f); - if(p->sbuflen < 0) { - ast_log(LOG_WARNING, "Short read (%d) (%s)!\n", p->sbuflen, strerror(errno)); - return -1; + if (p->sbuflen < MP3_SCACHE) { + if (ferror(s->f)) { + ast_log(LOG_WARNING, "Error while reading MP3 file: %s\n", strerror(errno)); + return -1; + } } res = decodeMP3(&p->mp,p->sbuf,p->sbuflen,p->dbuf,MP3_DCACHE,&p->dbuflen); if(res != MP3_OK) diff --git a/apps/app_minivm.c b/apps/app_minivm.c index 4cc2f4796..ff9ab340a 100644 --- a/apps/app_minivm.c +++ b/apps/app_minivm.c @@ -854,16 +854,16 @@ static int b64_inbuf(struct b64_baseio *bio, FILE *fi) if (bio->ateof) return 0; - if ((l = fread(bio->iobuf, 1, B64_BASEMAXINLINE,fi)) <= 0) { - if (ferror(fi)) - return -1; - + if ((l = fread(bio->iobuf, 1, B64_BASEMAXINLINE, fi)) != B64_BASEMAXINLINE) { bio->ateof = 1; - return 0; + if (l == 0) { + /* Assume EOF */ + return 0; + } } - bio->iolen= l; - bio->iocp= 0; + bio->iolen = l; + bio->iocp = 0; return 1; } diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c index de826704a..06f4830fa 100644 --- a/apps/app_voicemail.c +++ b/apps/app_voicemail.c @@ -2728,9 +2728,9 @@ static int imap_store_file(const char *dir, const char *mailboxuser, const char *(vmu->email) = '\0'; return -1; } - if (fread(buf, len, 1, p) < len) { + if (fread(buf, 1, len, p) != len) { if (ferror(p)) { - ast_log(LOG_ERROR, "Short read while reading in mail file.\n"); + ast_log(LOG_ERROR, "Error while reading mail file: %s\n"); return -1; } } @@ -4743,12 +4743,12 @@ static int inbuf(struct baseio *bio, FILE *fi) if (bio->ateof) return 0; - if ((l = fread(bio->iobuf, 1, BASEMAXINLINE, fi)) <= 0) { - if (ferror(fi)) - return -1; - + if ((l = fread(bio->iobuf, 1, BASEMAXINLINE, fi)) != BASEMAXINLINE) { bio->ateof = 1; - return 0; + if (l == 0) { + /* Assume EOF */ + return 0; + } } bio->iolen = l; diff --git a/channels/chan_sip.c b/channels/chan_sip.c index affe937e8..6fd7e8634 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -13097,7 +13097,7 @@ static void add_codec_to_sdp(const struct sip_pvt *p, /* Opus mandates 2 channels in rtpmap */ if (ast_format_cmp(format, ast_format_opus) == AST_FORMAT_CMP_EQUAL) { ast_str_append(a_buf, 0, "a=rtpmap:%d %s/%u/2\r\n", rtp_code, mime, rate); - } else if ((35 <= rtp_code) || !(sip_cfg.compactheaders)) { + } else if ((AST_RTP_PT_LAST_STATIC < rtp_code) || !(sip_cfg.compactheaders)) { ast_str_append(a_buf, 0, "a=rtpmap:%d %s/%u\r\n", rtp_code, mime, rate); } diff --git a/contrib/ast-db-manage/config/versions/1d0e332c32af_create_rls_table.py b/contrib/ast-db-manage/config/versions/1d0e332c32af_create_rls_table.py new file mode 100644 index 000000000..3557f0d52 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/1d0e332c32af_create_rls_table.py @@ -0,0 +1,39 @@ +"""create rls table + +Revision ID: 1d0e332c32af +Revises: 2da192dbbc65 +Create Date: 2017-04-25 12:50:09.412662 + +""" + +# revision identifiers, used by Alembic. +revision = '1d0e332c32af' +down_revision = '2da192dbbc65' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import ENUM + +YESNO_NAME = 'yesno_values' +YESNO_VALUES = ['yes', 'no'] + +def upgrade(): + ############################# Enums ############################## + + # yesno_values have already been created, so use postgres enum object + # type to get around "already created" issue - works okay with mysql + yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False) + + op.create_table( + 'ps_resource_list', + sa.Column('id', sa.String(40), nullable=False, unique=True), + sa.Column('list_item', sa.String(2048)), + sa.Column('event', sa.String(40)), + sa.Column('full_state', yesno_values), + sa.Column('notification_batch_interval', sa.Integer), + ) + + op.create_index('ps_resource_list_id', 'ps_resource_list', ['id']) + +def downgrade(): + op.drop_table('ps_resource_list') diff --git a/formats/format_g719.c b/formats/format_g719.c index 8cc942717..572b88f5f 100644 --- a/formats/format_g719.c +++ b/formats/format_g719.c @@ -45,8 +45,16 @@ static struct ast_frame *g719read(struct ast_filestream *s, int *whennext) AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, BUF_SIZE); if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) { - if (res) - ast_log(LOG_WARNING, "Short read (%d) (%s)!\n", res, strerror(errno)); + if (feof(s->f)) { + if (res) { + ast_log(LOG_WARNING, "Incomplete frame data at end of %s file " + "(expected %d bytes, read %d)\n", + ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res); + } + } else { + ast_log(LOG_ERROR, "Error while reading %s file: %s\n", + ast_format_get_name(s->fr.subclass.format), strerror(errno)); + } return NULL; } *whennext = s->fr.samples = BYTES_TO_SAMPLES(res); diff --git a/formats/format_g723.c b/formats/format_g723.c index 04e03b608..d4c4d4b1c 100644 --- a/formats/format_g723.c +++ b/formats/format_g723.c @@ -64,8 +64,17 @@ static struct ast_frame *g723_read(struct ast_filestream *s, int *whennext) } /* Read the data into the buffer */ AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, size); - if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != size) { - ast_log(LOG_WARNING, "Short read (%d of %d bytes) (%s)!\n", res, size, strerror(errno)); + if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) { + if (feof(s->f)) { + if (res) { + ast_log(LOG_WARNING, "Incomplete frame data at end of %s file " + "(expected %d bytes, read %d)\n", + ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res); + } + } else { + ast_log(LOG_ERROR, "Error while reading %s file: %s\n", + ast_format_get_name(s->fr.subclass.format), strerror(errno)); + } return NULL; } *whennext = s->fr.samples = 240; diff --git a/formats/format_g726.c b/formats/format_g726.c index 08e669e26..7da6d1ecb 100644 --- a/formats/format_g726.c +++ b/formats/format_g726.c @@ -124,8 +124,16 @@ static struct ast_frame *g726_read(struct ast_filestream *s, int *whennext) AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, frame_size[fs->rate]); s->fr.samples = 8 * FRAME_TIME; if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) { - if (res) - ast_log(LOG_WARNING, "Short read (%d) (%s)!\n", res, strerror(errno)); + if (feof(s->f)) { + if (res) { + ast_log(LOG_WARNING, "Incomplete frame data at end of %s file " + "(expected %d bytes, read %d)\n", + ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res); + } + } else { + ast_log(LOG_ERROR, "Error while reading %s file: %s\n", + ast_format_get_name(s->fr.subclass.format), strerror(errno)); + } return NULL; } *whennext = s->fr.samples; diff --git a/formats/format_g729.c b/formats/format_g729.c index 49e58025f..4cefc0401 100644 --- a/formats/format_g729.c +++ b/formats/format_g729.c @@ -51,8 +51,16 @@ static struct ast_frame *g729_read(struct ast_filestream *s, int *whennext) s->fr.samples = G729A_SAMPLES; AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, BUF_SIZE); if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) { - if (res && (res != 10)) /* XXX what for ? */ - ast_log(LOG_WARNING, "Short read (%d) (%s)!\n", res, strerror(errno)); + if (feof(s->f)) { + if (res) { + ast_log(LOG_WARNING, "Incomplete frame data at end of %s file " + "(expected %d bytes, read %d)\n", + ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res); + } + } else { + ast_log(LOG_ERROR, "Error while reading %s file: %s\n", + ast_format_get_name(s->fr.subclass.format), strerror(errno)); + } return NULL; } *whennext = s->fr.samples; diff --git a/formats/format_gsm.c b/formats/format_gsm.c index a2b6d3656..39deb983e 100644 --- a/formats/format_gsm.c +++ b/formats/format_gsm.c @@ -55,10 +55,18 @@ static struct ast_frame *gsm_read(struct ast_filestream *s, int *whennext) { int res; - AST_FRAME_SET_BUFFER(&(s->fr), s->buf, AST_FRIENDLY_OFFSET, GSM_FRAME_SIZE) + AST_FRAME_SET_BUFFER(&(s->fr), s->buf, AST_FRIENDLY_OFFSET, GSM_FRAME_SIZE); if ((res = fread(s->fr.data.ptr, 1, GSM_FRAME_SIZE, s->f)) != GSM_FRAME_SIZE) { - if (res) - ast_log(LOG_WARNING, "Short read (%d) (%s)!\n", res, strerror(errno)); + if (feof(s->f)) { + if (res) { + ast_log(LOG_WARNING, "Incomplete frame data at end of %s file " + "(expected %d bytes, read %d)\n", + ast_format_get_name(s->fr.subclass.format), GSM_FRAME_SIZE, res); + } + } else { + ast_log(LOG_ERROR, "Error while reading %s file: %s\n", + ast_format_get_name(s->fr.subclass.format), strerror(errno)); + } return NULL; } *whennext = s->fr.samples = GSM_SAMPLES; @@ -131,7 +139,7 @@ static int gsm_seek(struct ast_filestream *fs, off_t sample_offset, int whence) int i; fseeko(fs->f, 0, SEEK_END); for (i=0; i< (offset - max) / GSM_FRAME_SIZE; i++) { - if (!fwrite(gsm_silence, 1, GSM_FRAME_SIZE, fs->f)) { + if (fwrite(gsm_silence, 1, GSM_FRAME_SIZE, fs->f) != GSM_FRAME_SIZE) { ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno)); } } diff --git a/formats/format_h263.c b/formats/format_h263.c index 4cc3db542..d05e59825 100644 --- a/formats/format_h263.c +++ b/formats/format_h263.c @@ -58,7 +58,7 @@ static int h263_open(struct ast_filestream *s) { unsigned int ts; - if (fread(&ts, 1, sizeof(ts), s->f) < sizeof(ts)) { + if (fread(&ts, 1, sizeof(ts), s->f) != sizeof(ts)) { ast_log(LOG_WARNING, "Empty file!\n"); return -1; } @@ -74,7 +74,7 @@ static struct ast_frame *h263_read(struct ast_filestream *s, int *whennext) struct h263_desc *fs = (struct h263_desc *)s->_private; /* Send a frame from the file to the appropriate channel */ - if ((res = fread(&len, 1, sizeof(len), s->f)) < 1) + if ((res = fread(&len, 1, sizeof(len), s->f)) != sizeof(len)) return NULL; len = ntohs(len); mark = (len & FRAME_ENDED) ? 1 : 0; @@ -85,8 +85,16 @@ static struct ast_frame *h263_read(struct ast_filestream *s, int *whennext) } AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, len); if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) { - if (res) - ast_log(LOG_WARNING, "Short read (%d) (%s)!\n", res, strerror(errno)); + if (feof(s->f)) { + if (res) { + ast_log(LOG_WARNING, "Incomplete frame data at end of %s file " + "(expected %d bytes, read %d)\n", + ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res); + } + } else { + ast_log(LOG_ERROR, "Error while reading %s file: %s\n", + ast_format_get_name(s->fr.subclass.format), strerror(errno)); + } return NULL; } s->fr.samples = fs->lastts; /* XXX what ? */ diff --git a/formats/format_h264.c b/formats/format_h264.c index 60b090211..47f71ae6c 100644 --- a/formats/format_h264.c +++ b/formats/format_h264.c @@ -50,7 +50,7 @@ struct h264_desc { static int h264_open(struct ast_filestream *s) { unsigned int ts; - if (fread(&ts, 1, sizeof(ts), s->f) < sizeof(ts)) { + if (fread(&ts, 1, sizeof(ts), s->f) != sizeof(ts)) { ast_log(LOG_WARNING, "Empty file!\n"); return -1; } @@ -66,7 +66,7 @@ static struct ast_frame *h264_read(struct ast_filestream *s, int *whennext) struct h264_desc *fs = (struct h264_desc *)s->_private; /* Send a frame from the file to the appropriate channel */ - if ((res = fread(&len, 1, sizeof(len), s->f)) < 1) + if ((res = fread(&len, 1, sizeof(len), s->f)) != sizeof(len)) return NULL; len = ntohs(len); mark = (len & FRAME_ENDED) ? 1 : 0; @@ -77,8 +77,16 @@ static struct ast_frame *h264_read(struct ast_filestream *s, int *whennext) } AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, len); if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) { - if (res) - ast_log(LOG_WARNING, "Short read (%d of %d) (%s)!\n", res, len, strerror(errno)); + if (feof(s->f)) { + if (res) { + ast_log(LOG_WARNING, "Incomplete frame data at end of %s file " + "(expected %d bytes, read %d)\n", + ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res); + } + } else { + ast_log(LOG_ERROR, "Error while reading %s file: %s\n", + ast_format_get_name(s->fr.subclass.format), strerror(errno)); + } return NULL; } s->fr.samples = fs->lastts; diff --git a/formats/format_ilbc.c b/formats/format_ilbc.c index eab465d88..ec8ad0ffa 100644 --- a/formats/format_ilbc.c +++ b/formats/format_ilbc.c @@ -49,8 +49,16 @@ static struct ast_frame *ilbc_read(struct ast_filestream *s, int *whennext) /* Send a frame from the file to the appropriate channel */ AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, ILBC_BUF_SIZE); if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) { - if (res) - ast_log(LOG_WARNING, "Short read (%d) (%s)!\n", res, strerror(errno)); + if (feof(s->f)) { + if (res) { + ast_log(LOG_WARNING, "Incomplete frame data at end of %s file " + "(expected %d bytes, read %d)\n", + ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res); + } + } else { + ast_log(LOG_ERROR, "Error while reading %s file: %s\n", + ast_format_get_name(s->fr.subclass.format), strerror(errno)); + } return NULL; } *whennext = s->fr.samples = ILBC_SAMPLES; diff --git a/formats/format_ogg_vorbis.c b/formats/format_ogg_vorbis.c index c0f8c197d..4fdd1c411 100644 --- a/formats/format_ogg_vorbis.c +++ b/formats/format_ogg_vorbis.c @@ -181,10 +181,10 @@ static int ogg_vorbis_rewrite(struct ast_filestream *s, while (!tmp->eos) { if (ogg_stream_flush(&tmp->os, &tmp->og) == 0) break; - if (!fwrite(tmp->og.header, 1, tmp->og.header_len, s->f)) { + if (fwrite(tmp->og.header, 1, tmp->og.header_len, s->f) != tmp->og.header_len) { ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno)); } - if (!fwrite(tmp->og.body, 1, tmp->og.body_len, s->f)) { + if (fwrite(tmp->og.body, 1, tmp->og.body_len, s->f) != tmp->og.body_len) { ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno)); } if (ogg_page_eos(&tmp->og)) @@ -211,10 +211,10 @@ static void write_stream(struct ogg_vorbis_desc *s, FILE *f) if (ogg_stream_pageout(&s->os, &s->og) == 0) { break; } - if (!fwrite(s->og.header, 1, s->og.header_len, f)) { - ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno)); + if (fwrite(s->og.header, 1, s->og.header_len, f) != s->og.header_len) { + ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno)); } - if (!fwrite(s->og.body, 1, s->og.body_len, f)) { + if (fwrite(s->og.body, 1, s->og.body_len, f) != s->og.body_len) { ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno)); } if (ogg_page_eos(&s->og)) { diff --git a/formats/format_pcm.c b/formats/format_pcm.c index 97af0e9a3..f5ddda9c6 100644 --- a/formats/format_pcm.c +++ b/formats/format_pcm.c @@ -83,9 +83,17 @@ static struct ast_frame *pcm_read(struct ast_filestream *s, int *whennext) /* Send a frame from the file to the appropriate channel */ AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, BUF_SIZE); - if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) < 1) { - if (res) - ast_log(LOG_WARNING, "Short read (%d) (%s)!\n", res, strerror(errno)); + if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) { + if (feof(s->f)) { + if (res) { + ast_log(LOG_WARNING, "Incomplete frame data at end of %s file " + "(expected %d bytes, read %d)\n", + ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res); + } + } else { + ast_log(LOG_ERROR, "Error while reading %s file: %s\n", + ast_format_get_name(s->fr.subclass.format), strerror(errno)); + } return NULL; } s->fr.datalen = res; @@ -140,9 +148,10 @@ static int pcm_seek(struct ast_filestream *fs, off_t sample_offset, int whence) const char *src = (ast_format_cmp(fs->fmt->format, ast_format_alaw) == AST_FORMAT_CMP_EQUAL) ? alaw_silence : ulaw_silence; while (left) { - size_t written = fwrite(src, 1, (left > BUF_SIZE) ? BUF_SIZE : left, fs->f); - if (written == -1) + size_t written = fwrite(src, 1, MIN(left, BUF_SIZE), fs->f); + if (written < MIN(left, BUF_SIZE)) { break; /* error */ + } left -= written; } ret = 0; /* successful */ @@ -210,7 +219,10 @@ static int pcm_write(struct ast_filestream *fs, struct ast_frame *f) to_write = fpos - cur; if (to_write > sizeof(buf)) to_write = sizeof(buf); - fwrite(buf, 1, to_write, fs->f); + if (fwrite(buf, 1, to_write, fs->f) != to_write) { + ast_log(LOG_ERROR, "Failed to write to file: %s\n", strerror(errno)); + return -1; + } cur += to_write; } } diff --git a/formats/format_siren14.c b/formats/format_siren14.c index 1ce7d18ad..d54ed993b 100644 --- a/formats/format_siren14.c +++ b/formats/format_siren14.c @@ -45,8 +45,16 @@ static struct ast_frame *siren14read(struct ast_filestream *s, int *whennext) AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, BUF_SIZE); if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) { - if (res) - ast_log(LOG_WARNING, "Short read (%d) (%s)!\n", res, strerror(errno)); + if (feof(s->f)) { + if (res) { + ast_log(LOG_WARNING, "Incomplete frame data at end of %s file " + "(expected %d bytes, read %d)\n", + ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res); + } + } else { + ast_log(LOG_ERROR, "Error while reading %s file: %s\n", + ast_format_get_name(s->fr.subclass.format), strerror(errno)); + } return NULL; } *whennext = s->fr.samples = BYTES_TO_SAMPLES(res); diff --git a/formats/format_siren7.c b/formats/format_siren7.c index d20598445..f3b4b42b3 100644 --- a/formats/format_siren7.c +++ b/formats/format_siren7.c @@ -45,8 +45,16 @@ static struct ast_frame *siren7read(struct ast_filestream *s, int *whennext) AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, BUF_SIZE); if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) { - if (res) - ast_log(LOG_WARNING, "Short read (%d) (%s)!\n", res, strerror(errno)); + if (feof(s->f)) { + if (res) { + ast_log(LOG_WARNING, "Incomplete frame data at end of %s file " + "(expected %d bytes, read %d)\n", + ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res); + } + } else { + ast_log(LOG_ERROR, "Error while reading %s file: %s\n", + ast_format_get_name(s->fr.subclass.format), strerror(errno)); + } return NULL; } *whennext = s->fr.samples = BYTES_TO_SAMPLES(res); diff --git a/formats/format_sln.c b/formats/format_sln.c index af3f691c8..1977f7dc0 100644 --- a/formats/format_sln.c +++ b/formats/format_sln.c @@ -38,9 +38,17 @@ static struct ast_frame *generic_read(struct ast_filestream *s, int *whennext, u /* Send a frame from the file to the appropriate channel */ AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, buf_size); - if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) < 1) { - if (res) - ast_log(LOG_WARNING, "Short read (%d) (%s)!\n", res, strerror(errno)); + if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) { + if (feof(s->f)) { + if (res) { + ast_log(LOG_WARNING, "Incomplete frame data at end of %s file " + "(expected %d bytes, read %d)\n", + ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res); + } + } else { + ast_log(LOG_ERROR, "Error while reading %s file: %s\n", + ast_format_get_name(s->fr.subclass.format), strerror(errno)); + } return NULL; } *whennext = s->fr.samples = res/2; diff --git a/formats/format_vox.c b/formats/format_vox.c index 5a70c34b1..195714c6f 100644 --- a/formats/format_vox.c +++ b/formats/format_vox.c @@ -44,9 +44,17 @@ static struct ast_frame *vox_read(struct ast_filestream *s, int *whennext) /* Send a frame from the file to the appropriate channel */ AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, BUF_SIZE); - if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) < 1) { - if (res) - ast_log(LOG_WARNING, "Short read (%d) (%s)!\n", res, strerror(errno)); + if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) { + if (feof(s->f)) { + if (res) { + ast_log(LOG_WARNING, "Incomplete frame data at end of %s file " + "(expected %d bytes, read %d)\n", + ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res); + } + } else { + ast_log(LOG_ERROR, "Error while reading %s file: %s\n", + ast_format_get_name(s->fr.subclass.format), strerror(errno)); + } return NULL; } *whennext = s->fr.samples = res * 2; diff --git a/formats/format_wav.c b/formats/format_wav.c index 8316c3530..09e6a5313 100644 --- a/formats/format_wav.c +++ b/formats/format_wav.c @@ -361,7 +361,7 @@ static void wav_close(struct ast_filestream *s) /* Pad to even length */ if (fs->bytes & 0x1) { - if (!fwrite(&zero, 1, 1, s->f)) { + if (fwrite(&zero, 1, 1, s->f) != 1) { ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno)); } } @@ -385,14 +385,23 @@ static struct ast_frame *wav_read(struct ast_filestream *s, int *whennext) here = ftello(s->f); if (fs->maxlen - here < bytes) /* truncate if necessary */ bytes = fs->maxlen - here; - if (bytes < 0) - bytes = 0; + if (bytes <= 0) { + return NULL; + } /* ast_debug(1, "here: %d, maxlen: %d, bytes: %d\n", here, s->maxlen, bytes); */ AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, bytes); - - if ( (res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) <= 0 ) { - if (res) - ast_log(LOG_WARNING, "Short read (%d) (%s)!\n", res, strerror(errno)); + + if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) { + if (feof(s->f)) { + if (res) { + ast_log(LOG_WARNING, "Incomplete frame data at end of %s file " + "(expected %d bytes, read %d)\n", + ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res); + } + } else { + ast_log(LOG_ERROR, "Error while reading %s file: %s\n", + ast_format_get_name(s->fr.subclass.format), strerror(errno)); + } return NULL; } s->fr.datalen = res; diff --git a/formats/format_wav_gsm.c b/formats/format_wav_gsm.c index eef06cef5..bfec903d0 100644 --- a/formats/format_wav_gsm.c +++ b/formats/format_wav_gsm.c @@ -422,8 +422,16 @@ static struct ast_frame *wav_read(struct ast_filestream *s, int *whennext) int res; if ((res = fread(msdata, 1, MSGSM_FRAME_SIZE, s->f)) != MSGSM_FRAME_SIZE) { - if (res && (res != 1)) - ast_log(LOG_WARNING, "Short read (%d) (%s)!\n", res, strerror(errno)); + if (feof(s->f)) { + if (res) { + ast_log(LOG_WARNING, "Incomplete frame data at end of %s file " + "(expected %d bytes, read %d)\n", + ast_format_get_name(s->fr.subclass.format), MSGSM_FRAME_SIZE, res); + } + } else { + ast_log(LOG_ERROR, "Error while reading %s file: %s\n", + ast_format_get_name(s->fr.subclass.format), strerror(errno)); + } return NULL; } /* Convert from MS format to two real GSM frames */ @@ -511,7 +519,7 @@ static int wav_seek(struct ast_filestream *fs, off_t sample_offset, int whence) int i; fseek(fs->f, 0, SEEK_END); for (i=0; i< (offset - max) / MSGSM_FRAME_SIZE; i++) { - if (!fwrite(msgsm_silence, 1, MSGSM_FRAME_SIZE, fs->f)) { + if (fwrite(msgsm_silence, 1, MSGSM_FRAME_SIZE, fs->f) != MSGSM_FRAME_SIZE) { ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno)); } } diff --git a/include/asterisk/codec.h b/include/asterisk/codec.h index 2ae955181..2f5756cd1 100644 --- a/include/asterisk/codec.h +++ b/include/asterisk/codec.h @@ -166,6 +166,17 @@ int ast_codec_get_max(void); const char *ast_codec_media_type2str(enum ast_media_type type); /*! + * \brief Conversion function to take a media string and convert it to a media type + * + * \param media_type_str The media type string + * + * \retval The ast_media_type that corresponds to the string + * + * \since 15.0.0 + */ +enum ast_media_type ast_media_type_from_str(const char *media_type_str); + +/*! * \brief Get the number of samples contained within a frame * * \param frame The frame itself diff --git a/include/asterisk/format_cache.h b/include/asterisk/format_cache.h index 6099c59ea..92272e8eb 100644 --- a/include/asterisk/format_cache.h +++ b/include/asterisk/format_cache.h @@ -224,6 +224,11 @@ extern struct ast_format *ast_format_t140; extern struct ast_format *ast_format_t140_red; /*! + * \brief Built-in cached T.38 format. + */ +extern struct ast_format *ast_format_t38; + +/*! * \brief Built-in "null" format. */ extern struct ast_format *ast_format_none; diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h index fa7fed8a1..55acf6529 100644 --- a/include/asterisk/rtp_engine.h +++ b/include/asterisk/rtp_engine.h @@ -81,6 +81,12 @@ extern "C" { /*! Maximum number of payload types RTP can support. */ #define AST_RTP_MAX_PT 128 +/*! + * Last RTP payload type statically assigned, see + * http://www.iana.org/assignments/rtp-parameters + */ +#define AST_RTP_PT_LAST_STATIC 34 + /*! First dynamic RTP payload type */ #define AST_RTP_PT_FIRST_DYNAMIC 96 diff --git a/include/asterisk/sdp.h b/include/asterisk/sdp.h index 3649b4037..d5bf9147b 100644 --- a/include/asterisk/sdp.h +++ b/include/asterisk/sdp.h @@ -147,6 +147,22 @@ struct ast_sdp { }; /*! + * \brief A structure representing an SDP rtpmap attribute + */ +struct ast_sdp_rtpmap { + /*! The RTP payload number for the rtpmap */ + int payload; + /*! The Name of the codec */ + char *encoding_name; + /*! The clock rate of the codec */ + int clock_rate; + /*! Optional encoding parameters */ + char *encoding_parameters; + /*! Area where strings are stored */ + char buf[0]; +}; + +/*! * \brief Free an SDP Attribute * * \param a_line The attribute to free @@ -545,15 +561,88 @@ struct ast_sdp *ast_sdp_alloc(struct ast_sdp_o_line *o_line, struct ast_sdp_t_line *t_line); /*! - * \brief Create an SDP from an existing SDP State local topology + * \brief Find an attribute on the top-level SDP + * + * \note This will not search within streams for the given attribute. + * + * \param sdp The SDP in which to search + * \param attr_name The name of the attribute to search for + * \param payload Optional payload number to search for. If irrelevant, set to -1 + * + * \retval NULL Could not find the given attribute + * \retval Non-NULL The attribute to find + * + * \since 15.0.0 + */ +struct ast_sdp_a_line *ast_sdp_find_attribute(const struct ast_sdp *sdp, + const char *attr_name, int payload); + +/*! + * \brief Find an attribute on an SDP stream (m-line) * - * \param sdp_state SDP State + * \param sdp The SDP in which to search + * \param attr_name The name of the attribute to search for + * \param payload Optional payload number to search for. If irrelevant, set to -1 * + * \retval NULL Could not find the given attribute + * \retval Non-NULL The attribute to find + * + * \since 15.0.0 + */ +struct ast_sdp_a_line *ast_sdp_m_find_attribute(const struct ast_sdp_m_line *m_line, + const char *attr_name, int payload); + +/*! + * \brief Convert an SDP a_line into an rtpmap + * + * The returned value is heap-allocated and must be freed with + * ast_sdp_rtpmap_free() + * + * \param a_line The SDP a_line to convert + * + * \retval NULL Fail * \retval non-NULL Success - * \retval NULL Failure * - * \since 15 + * \since 15.0.0 */ -struct ast_sdp *ast_sdp_create_from_state(const struct ast_sdp_state *sdp_state); +struct ast_sdp_rtpmap *ast_sdp_a_get_rtpmap(const struct ast_sdp_a_line *a_line); + +/*! + * \brief Allocate a new SDP rtpmap + * + * \param payload The RTP payload number + * \param encoding_name The human-readable name for the codec + * \param clock_rate The rate of the codec, in cycles per second + * \param encoding_parameters Optional codec-specific parameters (such as number of channels) + * + * \retval NULL Fail + * \retval non-NULL Success + * + * \since 15.0.0 + */ +struct ast_sdp_rtpmap *ast_sdp_rtpmap_alloc(int payload, const char *encoding_name, + int clock_rate, const char *encoding_parameters); + +/*! + * \brief Free an SDP rtpmap + * + * \since 15.0.0 + */ +void ast_sdp_rtpmap_free(struct ast_sdp_rtpmap *rtpmap); + +/*! + * \brief Turn an SDP into a stream topology + * + * This traverses the m-lines of the SDP and creates a stream topology, with + * each m-line corresponding to a stream in the created topology. + * + * \param sdp The SDP to convert + * + * \retval NULL An error occurred when converting + * \retval non-NULL The generated stream topology + * + * \since 15.0.0 + */ +struct ast_stream_topology *ast_get_topology_from_sdp(const struct ast_sdp *sdp); #endif /* _SDP_PRIV_H */ diff --git a/include/asterisk/sdp_options.h b/include/asterisk/sdp_options.h index 0186eea57..af694cd14 100644 --- a/include/asterisk/sdp_options.h +++ b/include/asterisk/sdp_options.h @@ -19,6 +19,8 @@ #ifndef _ASTERISK_SDP_OPTIONS_H #define _ASTERISK_SDP_OPTIONS_H +#include "asterisk/udptl.h" + struct ast_sdp_options; /*! @@ -106,7 +108,7 @@ void ast_sdp_options_set_media_address(struct ast_sdp_options *options, * * \returns media_address */ -const char *ast_sdp_options_get_media_address(struct ast_sdp_options *options); +const char *ast_sdp_options_get_media_address(const struct ast_sdp_options *options); /*! * \since 15.0.0 @@ -126,7 +128,7 @@ void ast_sdp_options_set_sdpowner(struct ast_sdp_options *options, * * \returns sdpowner */ -const char *ast_sdp_options_get_sdpowner(struct ast_sdp_options *options); +const char *ast_sdp_options_get_sdpowner(const struct ast_sdp_options *options); /*! * \since 15.0.0 @@ -146,7 +148,7 @@ void ast_sdp_options_set_sdpsession(struct ast_sdp_options *options, * * \returns sdpsession */ -const char *ast_sdp_options_get_sdpsession(struct ast_sdp_options *options); +const char *ast_sdp_options_get_sdpsession(const struct ast_sdp_options *options); /*! * \since 15.0.0 @@ -166,7 +168,7 @@ void ast_sdp_options_set_rtp_engine(struct ast_sdp_options *options, * * \returns rtp_engine */ -const char *ast_sdp_options_get_rtp_engine(struct ast_sdp_options *options); +const char *ast_sdp_options_get_rtp_engine(const struct ast_sdp_options *options); /*! * \since 15.0.0 @@ -186,7 +188,7 @@ void ast_sdp_options_set_bind_rtp_to_media_address(struct ast_sdp_options *optio * * \returns bind_rtp_to_media_address */ -unsigned int ast_sdp_options_get_bind_rtp_to_media_address(struct ast_sdp_options *options); +unsigned int ast_sdp_options_get_bind_rtp_to_media_address(const struct ast_sdp_options *options); /*! * \since 15.0.0 @@ -206,7 +208,7 @@ void ast_sdp_options_set_rtp_symmetric(struct ast_sdp_options *options, * * \returns rtp_symmetric */ -unsigned int ast_sdp_options_get_rtp_symmetric(struct ast_sdp_options *options); +unsigned int ast_sdp_options_get_rtp_symmetric(const struct ast_sdp_options *options); /*! * \since 15.0.0 @@ -226,7 +228,7 @@ void ast_sdp_options_set_telephone_event(struct ast_sdp_options *options, * * \returns telephone_event */ -unsigned int ast_sdp_options_get_telephone_event(struct ast_sdp_options *options); +unsigned int ast_sdp_options_get_telephone_event(const struct ast_sdp_options *options); /*! * \since 15.0.0 @@ -246,7 +248,7 @@ void ast_sdp_options_set_rtp_ipv6(struct ast_sdp_options *options, * * \returns rtp_ipv6 */ -unsigned int ast_sdp_options_get_rtp_ipv6(struct ast_sdp_options *options); +unsigned int ast_sdp_options_get_rtp_ipv6(const struct ast_sdp_options *options); /*! * \since 15.0.0 @@ -266,7 +268,7 @@ void ast_sdp_options_set_g726_non_standard(struct ast_sdp_options *options, * * \returns g726_non_standard */ -unsigned int ast_sdp_options_get_g726_non_standard(struct ast_sdp_options *options); +unsigned int ast_sdp_options_get_g726_non_standard(const struct ast_sdp_options *options); /*! * \since 15.0.0 @@ -286,7 +288,7 @@ void ast_sdp_options_set_tos_audio(struct ast_sdp_options *options, * * \returns tos_audio */ -unsigned int ast_sdp_options_get_tos_audio(struct ast_sdp_options *options); +unsigned int ast_sdp_options_get_tos_audio(const struct ast_sdp_options *options); /*! * \since 15.0.0 @@ -306,7 +308,7 @@ void ast_sdp_options_set_cos_audio(struct ast_sdp_options *options, * * \returns cos_audio */ -unsigned int ast_sdp_options_get_cos_audio(struct ast_sdp_options *options); +unsigned int ast_sdp_options_get_cos_audio(const struct ast_sdp_options *options); /*! * \since 15.0.0 @@ -326,7 +328,7 @@ void ast_sdp_options_set_tos_video(struct ast_sdp_options *options, * * \returns tos_video */ -unsigned int ast_sdp_options_get_tos_video(struct ast_sdp_options *options); +unsigned int ast_sdp_options_get_tos_video(const struct ast_sdp_options *options); /*! * \since 15.0.0 @@ -346,7 +348,7 @@ void ast_sdp_options_set_cos_video(struct ast_sdp_options *options, * * \returns cos_video */ -unsigned int ast_sdp_options_get_cos_video(struct ast_sdp_options *options); +unsigned int ast_sdp_options_get_cos_video(const struct ast_sdp_options *options); /*! * \since 15.0.0 @@ -366,7 +368,7 @@ void ast_sdp_options_set_ice(struct ast_sdp_options *options, * * \returns ice */ -enum ast_sdp_options_ice ast_sdp_options_get_ice(struct ast_sdp_options *options); +enum ast_sdp_options_ice ast_sdp_options_get_ice(const struct ast_sdp_options *options); /*! * \since 15.0.0 @@ -386,7 +388,7 @@ void ast_sdp_options_set_impl(struct ast_sdp_options *options, * * \returns impl */ -enum ast_sdp_options_impl ast_sdp_options_get_impl(struct ast_sdp_options *options); +enum ast_sdp_options_impl ast_sdp_options_get_impl(const struct ast_sdp_options *options); /*! * \since 15.0.0 @@ -406,6 +408,105 @@ void ast_sdp_options_set_encryption(struct ast_sdp_options *options, * * \returns encryption */ -enum ast_sdp_options_encryption ast_sdp_options_get_encryption(struct ast_sdp_options *options); +enum ast_sdp_options_encryption ast_sdp_options_get_encryption(const struct ast_sdp_options *options); + +/*! + * \since 15.0.0 + * \brief Get SDP Options RTCP MUX + * + * \param options SDP Options + * + * \returns Boolean indicating if RTCP MUX is enabled. + */ +unsigned int ast_sdp_options_get_rtcp_mux(const struct ast_sdp_options *options); + +/*! + * \since 15.0.0 + * \brief Set SDP Options RTCP MUX + * + * \param options SDP Options + * \param value Boolean that indicates if RTCP MUX should be enabled. + */ +void ast_sdp_options_set_rtcp_mux(struct ast_sdp_options *options, unsigned int value); + +/*! + * \since 15.0.0 + * \brief Set SDP Options udptl_symmetric + * + * \param options SDP Options + * \param udptl_symmetric + */ +void ast_sdp_options_set_udptl_symmetric(struct ast_sdp_options *options, + unsigned int udptl_symmetric); + +/*! + * \since 15.0.0 + * \brief Get SDP Options udptl_symmetric + * + * \param options SDP Options + * + * \returns udptl_symmetric + */ +unsigned int ast_sdp_options_get_udptl_symmetric(const struct ast_sdp_options *options); + +/*! + * \since 15.0.0 + * \brief Set SDP Options udptl_error_correction + * + * \param options SDP Options + * \param error_correction + */ +void ast_sdp_options_set_udptl_error_correction(struct ast_sdp_options *options, + enum ast_t38_ec_modes error_correction); + +/*! + * \since 15.0.0 + * \brief Get SDP Options udptl_error_correction + * + * \param options SDP Options + * + * \returns udptl_error_correction + */ +enum ast_t38_ec_modes ast_sdp_options_get_udptl_error_correction(const struct ast_sdp_options *options); + +/*! + * \since 15.0.0 + * \brief Set SDP Options udptl_far_max_datagram + * + * \param options SDP Options + * \param far_max_datagram + */ +void ast_sdp_options_set_udptl_far_max_datagram(struct ast_sdp_options *options, + unsigned int far_max_datagram); + +/*! + * \since 15.0.0 + * \brief Get SDP Options udptl_far_max_datagram + * + * \param options SDP Options + * + * \returns udptl_far_max_datagram + */ +unsigned int ast_sdp_options_get_udptl_far_max_datagram(const struct ast_sdp_options *options); + +/*! + * \since 15.0.0 + * \brief Set SDP Options bind_udptl_to_media_address + * + * \param options SDP Options + * \param bind_udptl_to_media_address + */ +void ast_sdp_options_set_bind_udptl_to_media_address(struct ast_sdp_options *options, + unsigned int bind_udptl_to_media_address); + +/*! + * \since 15.0.0 + * \brief Get SDP Options bind_udptl_to_media_address + * + * \param options SDP Options + * + * \returns bind_udptl_to_media_address + */ +unsigned int ast_sdp_options_get_bind_udptl_to_media_address(const struct ast_sdp_options *options); #endif /* _ASTERISK_SDP_OPTIONS_H */ diff --git a/include/asterisk/sdp_state.h b/include/asterisk/sdp_state.h index a186d7eef..1382ed6af 100644 --- a/include/asterisk/sdp_state.h +++ b/include/asterisk/sdp_state.h @@ -24,6 +24,8 @@ struct ast_sdp_state; struct ast_sockaddr; +struct ast_udptl; +struct ast_control_t38_parameters; /*! * \brief Allocate a new SDP state @@ -52,6 +54,14 @@ struct ast_rtp_instance *ast_sdp_state_get_rtp_instance(const struct ast_sdp_sta int stream_index); /*! + * \brief Get the associated UDPTL instance for a particular stream on the SDP state. + * + * Stream numbers correspond to the streams in the topology of the associated channel + */ +struct ast_udptl *ast_sdp_state_get_udptl_instance(const struct ast_sdp_state *sdp_state, + int stream_index); + +/*! * \brief Get the global connection address on the SDP state. */ const struct ast_sockaddr *ast_sdp_state_get_connection_address(const struct ast_sdp_state *sdp_state); @@ -138,7 +148,7 @@ const void *ast_sdp_state_get_local_sdp_impl(struct ast_sdp_state *sdp_state); * * \since 15 */ -void ast_sdp_state_set_remote_sdp(struct ast_sdp_state *sdp_state, struct ast_sdp *sdp); +void ast_sdp_state_set_remote_sdp(struct ast_sdp_state *sdp_state, const struct ast_sdp *sdp); /*! * \brief Set the remote SDP from an Implementation @@ -225,6 +235,17 @@ void ast_sdp_state_set_locally_held(struct ast_sdp_state *sdp_state, /*! * \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); + +/*! + * \since 15.0.0 * \brief Get whether a stream is held or not * * \param sdp_state diff --git a/main/codec.c b/main/codec.c index 1870c393b..7797147a7 100644 --- a/main/codec.c +++ b/main/codec.c @@ -376,6 +376,21 @@ const char *ast_codec_media_type2str(enum ast_media_type type) } } +enum ast_media_type ast_media_type_from_str(const char *media_type_str) +{ + if (!strcasecmp(media_type_str, "audio")) { + return AST_MEDIA_TYPE_AUDIO; + } else if (!strcasecmp(media_type_str, "video")) { + return AST_MEDIA_TYPE_VIDEO; + } else if (!strcasecmp(media_type_str, "image")) { + return AST_MEDIA_TYPE_IMAGE; + } else if (!strcasecmp(media_type_str, "text")) { + return AST_MEDIA_TYPE_TEXT; + } else { + return AST_MEDIA_TYPE_UNKNOWN; + } +} + unsigned int ast_codec_samples_count(struct ast_frame *frame) { struct ast_codec *codec; diff --git a/main/codec_builtin.c b/main/codec_builtin.c index f622c9105..3320900c2 100644 --- a/main/codec_builtin.c +++ b/main/codec_builtin.c @@ -822,6 +822,12 @@ static struct ast_codec t140 = { .type = AST_MEDIA_TYPE_TEXT, }; +static struct ast_codec t38 = { + .name = "t38", + .description = "T.38 UDPTL Fax", + .type = AST_MEDIA_TYPE_IMAGE, +}; + static int silk_samples(struct ast_frame *frame) { /* XXX This is likely not at all what's intended from this callback. However, @@ -952,6 +958,7 @@ int ast_codec_builtin_init(void) res |= CODEC_REGISTER_AND_CACHE(vp8); res |= CODEC_REGISTER_AND_CACHE(t140red); res |= CODEC_REGISTER_AND_CACHE(t140); + res |= CODEC_REGISTER_AND_CACHE(t38); res |= CODEC_REGISTER_AND_CACHE(none); res |= CODEC_REGISTER_AND_CACHE_NAMED("silk8", silk8); res |= CODEC_REGISTER_AND_CACHE_NAMED("silk12", silk12); diff --git a/main/format_cache.c b/main/format_cache.c index d0ae32e68..302bbf827 100644 --- a/main/format_cache.c +++ b/main/format_cache.c @@ -231,6 +231,11 @@ struct ast_format *ast_format_t140; struct ast_format *ast_format_t140_red; /*! + * \brief Built-in cached T.38 format. + */ +struct ast_format *ast_format_t38; + +/*! * \brief Built-in "null" format. */ struct ast_format *ast_format_none; @@ -342,6 +347,7 @@ static void format_cache_shutdown(void) ao2_replace(ast_format_vp8, NULL); ao2_replace(ast_format_t140_red, NULL); ao2_replace(ast_format_t140, NULL); + ao2_replace(ast_format_t38, NULL); ao2_replace(ast_format_none, NULL); ao2_replace(ast_format_silk8, NULL); ao2_replace(ast_format_silk12, NULL); @@ -442,6 +448,8 @@ static void set_cached_format(const char *name, struct ast_format *format) ao2_replace(ast_format_t140_red, format); } else if (!strcmp(name, "t140")) { ao2_replace(ast_format_t140, format); + } else if (!strcmp(name, "t38")) { + ao2_replace(ast_format_t38, format); } else if (!strcmp(name, "none")) { ao2_replace(ast_format_none, format); } else if (!strcmp(name, "silk8")) { diff --git a/main/rtp_engine.c b/main/rtp_engine.c index 0a2e84fcd..82298901d 100644 --- a/main/rtp_engine.c +++ b/main/rtp_engine.c @@ -1426,28 +1426,31 @@ static int find_unused_payload(const struct ast_rtp_codecs *codecs) * https://tools.ietf.org/html/draft-roach-mmusic-unified-plan#section-3.2.1.2 * https://tools.ietf.org/html/draft-wu-avtcore-dynamic-pt-usage#section-3 */ - res = find_unused_payload_in_range(codecs, MAX(ast_option_rtpptdynamic, 35), + res = find_unused_payload_in_range( + codecs, MAX(ast_option_rtpptdynamic, AST_RTP_PT_LAST_STATIC + 1), AST_RTP_PT_LAST_REASSIGN, static_RTP_PT); if (res != -1) { return res; } - /* Yet, reusing mappings below 35 is not supported in Asterisk because - * when Compact Headers are activated, no rtpmap is send for those below - * 35. If you want to use 35 and below + /* Yet, reusing mappings below AST_RTP_PT_LAST_STATIC (35) is not supported + * in Asterisk because when Compact Headers are activated, no rtpmap is + * send for those below 35. If you want to use 35 and below * A) do not use Compact Headers, * B) remove that code in chan_sip/res_pjsip, or * C) add a flag that this RTP Payload Type got reassigned dynamically * and requires a rtpmap even with Compact Headers enabled. */ res = find_unused_payload_in_range( - codecs, MAX(ast_option_rtpptdynamic, 20), 35, static_RTP_PT); + codecs, MAX(ast_option_rtpptdynamic, 20), + AST_RTP_PT_LAST_STATIC + 1, static_RTP_PT); if (res != -1) { return res; } return find_unused_payload_in_range( - codecs, MAX(ast_option_rtpptdynamic, 0), 20, static_RTP_PT); + codecs, MAX(ast_option_rtpptdynamic, 0), + 20, static_RTP_PT); } /*! diff --git a/main/sdp.c b/main/sdp.c index 1ef6400b9..dc6afe7d8 100644 --- a/main/sdp.c +++ b/main/sdp.c @@ -23,6 +23,7 @@ #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" @@ -500,201 +501,290 @@ int ast_sdp_m_add_format(struct ast_sdp_m_line *m_line, const struct ast_sdp_opt 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) +static struct ast_sdp_a_line *sdp_find_attribute_common(const struct ast_sdp_a_lines *a_lines, + const char *attr_name, int payload) { - 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; + int i; - ast_assert(sdp && options && stream); + for (i = 0; i < AST_VECTOR_SIZE(a_lines); ++i) { + int a_line_payload; - media_type = ast_stream_get_type(stream); - if (ast_sdp_state_get_stream_connection_address(sdp_state, 0, &address_rtp)) { - return -1; - } + a_line = AST_VECTOR_GET(a_lines, i); + if (strcmp(a_line->name, attr_name)) { + continue; + } - 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; + 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 a_line; + } + } else { + return a_line; + } } - 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); + return NULL; +} - 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; - } +struct ast_sdp_a_line *ast_sdp_find_attribute(const struct ast_sdp *sdp, + const char *attr_name, int payload) +{ + return sdp_find_attribute_common(sdp->a_lines, attr_name, payload); +} - 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; - } +struct ast_sdp_a_line *ast_sdp_m_find_attribute(const struct ast_sdp_m_line *m_line, + const char *attr_name, int payload) +{ + return sdp_find_attribute_common(m_line->a_lines, attr_name, payload); +} - 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); - } +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; - ao2_ref(format, -1); + rtpmap = ast_calloc(1, sizeof(*rtpmap) + strlen(encoding_name) + strlen(encoding_parameters) + 2); + if (!rtpmap) { + return NULL; } - if (media_type != AST_MEDIA_TYPE_VIDEO) { - for (i = 1LL; i <= AST_RTP_MAX; i <<= 1) { - if (!(options->telephone_event & i)) { - continue; - } + rtpmap->payload = payload; + rtpmap->clock_rate = clock_rate; - rtp_code = ast_rtp_codecs_payload_code( - ast_rtp_instance_get_codecs(rtp), 0, NULL, i); + 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); - if (rtp_code == -1) { - continue; - } + return rtpmap; +} - if (sdp_m_add_rtpmap(m_line, options, rtp_code, 0, NULL, i)) { - continue; - } +void ast_sdp_rtpmap_free(struct ast_sdp_rtpmap *rtpmap) +{ + ast_free(rtpmap); +} - 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; - } - } - } - } +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; - if (ast_sdp_m_get_a_count(m_line) == 0) { - return 0; - } + value_copy = ast_strip(ast_strdupa(a_line->value)); - /* 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 (sscanf(value_copy, "%30d %63s", &payload, encoding_name) != 2) { + return NULL; } - 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; - } + 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; } - 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; + 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 = ""; } - 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; - } + rtpmap = ast_sdp_rtpmap_alloc(payload, encoding_name, clock_rate, + encoding_parameters); - if (ast_sdp_add_m(sdp, m_line)) { - ast_sdp_m_free(m_line); - return -1; + 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 0; + return ast_sdp_a_get_rtpmap(rtpmap_attr); } -struct ast_sdp *ast_sdp_create_from_state(const struct ast_sdp_state *sdp_state) +/*! + * \brief Find and process fmtp attributes for a given payload + * + * \param m_line The stream on which to search for the fmtp attribute + * \param payload The specific fmtp attribute to search for + * \param codecs The current RTP codecs that have been built up + */ +static void process_fmtp(const struct ast_sdp_m_line *m_line, int payload, + struct ast_rtp_codecs *codecs) { - 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); + struct ast_sdp_a_line *attr; + char *param; + char *param_start; + char *param_end; + size_t len; + struct ast_format *replace; + struct ast_format *format; - t = tv.tv_sec + 2208988800UL; - address_type = (strchr(options->media_address, ':') ? "IP6" : "IP4"); + attr = ast_sdp_m_find_attribute(m_line, "fmtp", payload); + if (!attr) { + return; + } - o_line = ast_sdp_o_alloc(options->sdpowner, t, t, address_type, options->media_address); - if (!o_line) { - goto error; + /* Extract the "a=fmtp:%d %s" attribute parameter string after the payload type. */ + param_start = ast_skip_nonblanks(attr->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; } - c_line = ast_sdp_c_alloc(address_type, options->media_address); - if (!c_line) { - goto error; + 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; } - s_line = ast_sdp_s_alloc(options->sdpsession); - if (!s_line) { - goto error; + 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); +} - sdp = ast_sdp_alloc(o_line, c_line, s_line, NULL); - if (!sdp) { - goto error; +/*! + * \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 m_line The SDP media section to convert + * \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 i; + int non_ast_fmts; + struct ast_rtp_codecs codecs; + struct ast_format_cap *caps; + struct ast_stream *stream; + + 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: + ast_rtp_codecs_payloads_initialize(&codecs); - 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)); + 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; - 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; + payload_s = ast_sdp_m_get_payload(m_line, i); + sscanf(payload_s->fmt, "%30d", &payload); + ast_rtp_codecs_payloads_set_m_type(&codecs, NULL, payload); + + rtpmap = sdp_payload_get_rtpmap(m_line, payload); + if (!rtpmap) { + continue; } + ast_rtp_codecs_payloads_set_rtpmap_type_rate(&codecs, NULL, + payload, m_line->type, rtpmap->encoding_name, 0, + rtpmap->clock_rate); + ast_sdp_rtpmap_free(rtpmap); + + process_fmtp(m_line, payload, &codecs); } + + ast_rtp_codecs_payload_formats(&codecs, caps, &non_ast_fmts); + ast_rtp_codecs_payloads_destroy(&codecs); + break; + case AST_MEDIA_TYPE_IMAGE: + 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); + } + } + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; } - return sdp; + ast_stream_set_formats(stream, caps); + ao2_ref(caps, -1); -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 stream; +} + +struct ast_stream_topology *ast_get_topology_from_sdp(const struct ast_sdp *sdp) +{ + struct ast_stream_topology *topology; + int i; + + topology = ast_stream_topology_alloc(); + if (!topology) { + return NULL; } - return NULL; -} + 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)); + if (!stream) { + continue; + } + ast_stream_topology_append_stream(topology, stream); + } + return topology; +} diff --git a/main/sdp_options.c b/main/sdp_options.c index 608481722..ef056a190 100644 --- a/main/sdp_options.c +++ b/main/sdp_options.c @@ -36,7 +36,7 @@ void ast_sdp_options_set_##field(struct ast_sdp_options *options, const char *va if (!strcmp(value, options->field)) return; \ ast_string_field_set(options, field, value); \ } \ -const char *ast_sdp_options_get_##field(struct ast_sdp_options *options) \ +const char *ast_sdp_options_get_##field(const struct ast_sdp_options *options) \ { \ ast_assert(options != NULL); \ return options->field; \ @@ -48,7 +48,7 @@ void ast_sdp_options_set_##field(struct ast_sdp_options *options, type value) \ ast_assert(options != NULL); \ options->field = value; \ } \ -type ast_sdp_options_get_##field(struct ast_sdp_options *options) \ +type ast_sdp_options_get_##field(const struct ast_sdp_options *options) \ { \ ast_assert(options != NULL); \ return options->field; \ @@ -60,10 +60,15 @@ DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(sdpsession, 0); DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(rtp_engine, 0); DEFINE_GETTERS_SETTERS_FOR(unsigned int, bind_rtp_to_media_address); +DEFINE_GETTERS_SETTERS_FOR(unsigned int, bind_udptl_to_media_address); 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); +DEFINE_GETTERS_SETTERS_FOR(unsigned int, udptl_far_max_datagram); DEFINE_GETTERS_SETTERS_FOR(unsigned int, telephone_event); DEFINE_GETTERS_SETTERS_FOR(unsigned int, rtp_ipv6); DEFINE_GETTERS_SETTERS_FOR(unsigned int, g726_non_standard); +DEFINE_GETTERS_SETTERS_FOR(unsigned int, rtcp_mux); DEFINE_GETTERS_SETTERS_FOR(unsigned int, tos_audio); DEFINE_GETTERS_SETTERS_FOR(unsigned int, cos_audio); DEFINE_GETTERS_SETTERS_FOR(unsigned int, tos_video); diff --git a/main/sdp_private.h b/main/sdp_private.h index 45aaebf9a..f2efe86e5 100644 --- a/main/sdp_private.h +++ b/main/sdp_private.h @@ -35,21 +35,26 @@ struct ast_sdp_options { ); struct { unsigned int bind_rtp_to_media_address : 1; + unsigned int bind_udptl_to_media_address : 1; unsigned int rtp_symmetric : 1; + unsigned int udptl_symmetric : 1; unsigned int telephone_event : 1; unsigned int rtp_ipv6 : 1; unsigned int g726_non_standard : 1; unsigned int locally_held : 1; + unsigned int rtcp_mux: 1; }; struct { unsigned int tos_audio; unsigned int cos_audio; unsigned int tos_video; unsigned int cos_video; + unsigned int udptl_far_max_datagram; }; enum ast_sdp_options_ice ice; enum ast_sdp_options_impl impl; enum ast_sdp_options_encryption encryption; + enum ast_t38_ec_modes udptl_error_correction; }; #endif /* _MAIN_SDP_PRIVATE_H */ diff --git a/main/sdp_state.c b/main/sdp_state.c index 5858a65ab..5aee567d3 100644 --- a/main/sdp_state.c +++ b/main/sdp_state.c @@ -24,86 +24,317 @@ #include "asterisk/utils.h" #include "asterisk/netsock2.h" #include "asterisk/rtp_engine.h" +#include "asterisk/format.h" +#include "asterisk/format_cap.h" +#include "asterisk/config.h" +#include "asterisk/codec.h" +#include "asterisk/udptl.h" #include "../include/asterisk/sdp.h" #include "asterisk/stream.h" #include "sdp_private.h" -enum ast_sdp_state_machine { - /*! \brief The initial state. +enum ast_sdp_role { + /*! + * \brief The role has not yet been determined. * - * The state machine starts here. It also goes back to this - * state whenever ast_sdp_state_reset() is called. + * When the SDP state is allocated, this is the starting role. + * Similarly, when the SDP state is reset, the role is reverted + * to this. */ - SDP_STATE_INITIAL, - /*! \brief We are the SDP offerer. + SDP_ROLE_NOT_SET, + /*! + * \brief We are the offerer. * - * The state machine enters this state if in the initial state - * and ast_sdp_state_get_local() is called. When this state is - * entered, a local SDP is created and then returned. + * If a local SDP is requested before a remote SDP has been set, then + * we assume the role of offerer. This means that we will generate an + * SDP from the local capabilities and configured options. */ - SDP_STATE_OFFERER, - /*! \brief We are the SDP answerer. + SDP_ROLE_OFFERER, + /*! + * \brief We are the answerer. * - * The state machine enters this state if in the initial state - * and ast_sdp_state_set_remote() is called. + * If a remote SDP is set before a local SDP is requested, then we + * assume the role of answerer. This means that we will generate an + * SDP based on a merge of the remote capabilities and our local capabilities. */ - SDP_STATE_ANSWERER, - /*! \brief The SDP has been negotiated. - * - * This state can be entered from either the offerer or answerer - * state. When this state is entered, a joint SDP is created. - */ - SDP_STATE_NEGOTIATED, - /*! \brief Not an actual state. - * - * This is just here to mark the end of the enumeration. - */ - SDP_STATE_END, + SDP_ROLE_ANSWERER, }; typedef int (*state_fn)(struct ast_sdp_state *state); +struct sdp_state_udptl { + /*! The underlying UDPTL instance */ + struct ast_udptl *instance; +}; + struct sdp_state_stream { + /*! Type of the stream */ + enum ast_media_type type; union { /*! The underlying RTP instance */ struct ast_rtp_instance *instance; + /*! The underlying UDPTL instance */ + struct sdp_state_udptl *udptl; }; /*! An explicit connection address for this stream */ struct ast_sockaddr connection_address; /*! Whether this stream is held or not */ unsigned int locally_held; + /*! UDPTL session parameters */ + struct ast_control_t38_parameters t38_local_params; }; +static void sdp_state_udptl_destroy(void *obj) +{ + struct sdp_state_udptl *udptl = obj; + + if (udptl->instance) { + ast_udptl_destroy(udptl->instance); + } +} + +static void sdp_state_stream_free(struct sdp_state_stream *state_stream) +{ + switch (state_stream->type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + if (state_stream->instance) { + ast_rtp_instance_destroy(state_stream->instance); + } + break; + case AST_MEDIA_TYPE_IMAGE: + ao2_cleanup(state_stream->udptl); + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; + } + ast_free(state_stream); +} + +AST_VECTOR(sdp_state_streams, struct sdp_state_stream *); + struct sdp_state_capabilities { /*! Stream topology */ struct ast_stream_topology *topology; /*! Additional information about the streams */ - AST_VECTOR(, struct sdp_state_stream) 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) +{ + if (!capabilities) { + return; + } + + ast_stream_topology_free(capabilities->topology); + AST_VECTOR_CALLBACK_VOID(&capabilities->streams, sdp_state_stream_free); + AST_VECTOR_FREE(&capabilities->streams); + ast_free(capabilities); +} + +/* TODO + * This isn't set anywhere yet. + */ +/*! \brief Scheduler for RTCP purposes */ +static struct ast_sched_context *sched; + +/*! \brief Internal function which creates an RTP instance */ +static struct ast_rtp_instance *create_rtp(const struct ast_sdp_options *options, + enum ast_media_type media_type) +{ + struct ast_rtp_instance *rtp; + struct ast_rtp_engine_ice *ice; + struct ast_sockaddr temp_media_address; + static struct ast_sockaddr address_rtp; + struct ast_sockaddr *media_address = &address_rtp; + + if (options->bind_rtp_to_media_address && !ast_strlen_zero(options->media_address)) { + ast_sockaddr_parse(&temp_media_address, options->media_address, 0); + media_address = &temp_media_address; + } else { + if (ast_check_ipv6()) { + ast_sockaddr_parse(&address_rtp, "::", 0); + } else { + ast_sockaddr_parse(&address_rtp, "0.0.0.0", 0); + } + } + + if (!(rtp = ast_rtp_instance_new(options->rtp_engine, sched, media_address, NULL))) { + ast_log(LOG_ERROR, "Unable to create RTP instance using RTP engine '%s'\n", + options->rtp_engine); + return NULL; + } + + ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_RTCP, 1); + ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_NAT, options->rtp_symmetric); + + if (options->ice == AST_SDP_ICE_DISABLED && (ice = ast_rtp_instance_get_ice(rtp))) { + ice->stop(rtp); + } + + if (options->telephone_event) { + ast_rtp_instance_dtmf_mode_set(rtp, AST_RTP_DTMF_MODE_RFC2833); + ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_DTMF, 1); + } + + if (media_type == AST_MEDIA_TYPE_AUDIO && + (options->tos_audio || options->cos_audio)) { + ast_rtp_instance_set_qos(rtp, options->tos_audio, + options->cos_audio, "SIP RTP Audio"); + } else if (media_type == AST_MEDIA_TYPE_VIDEO && + (options->tos_video || options->cos_video)) { + ast_rtp_instance_set_qos(rtp, options->tos_video, + options->cos_video, "SIP RTP Video"); + } + + ast_rtp_instance_set_last_rx(rtp, time(NULL)); + + return rtp; +} + +/*! \brief Internal function which creates a UDPTL instance */ +static struct sdp_state_udptl *create_udptl(const struct ast_sdp_options *options) +{ + struct sdp_state_udptl *udptl; + struct ast_sockaddr temp_media_address; + static struct ast_sockaddr address_udptl; + struct ast_sockaddr *media_address = &address_udptl; + + if (options->bind_udptl_to_media_address && !ast_strlen_zero(options->media_address)) { + ast_sockaddr_parse(&temp_media_address, options->media_address, 0); + media_address = &temp_media_address; + } else { + if (ast_check_ipv6()) { + ast_sockaddr_parse(&address_udptl, "::", 0); + } else { + ast_sockaddr_parse(&address_udptl, "0.0.0.0", 0); + } + } + + udptl = ao2_alloc_options(sizeof(*udptl), sdp_state_udptl_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!udptl) { + return NULL; + } + + udptl->instance = ast_udptl_new_with_bindaddr(NULL, NULL, 0, media_address); + if (!udptl->instance) { + ao2_ref(udptl, -1); + return NULL; + } + + ast_udptl_set_error_correction_scheme(udptl->instance, ast_sdp_options_get_udptl_error_correction(options)); + ast_udptl_setnat(udptl->instance, ast_sdp_options_get_udptl_symmetric(options)); + ast_udptl_set_far_max_datagram(udptl->instance, ast_sdp_options_get_udptl_far_max_datagram(options)); + + return udptl; +} + +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; + + capabilities = ast_calloc(1, sizeof(*capabilities)); + if (!capabilities) { + return NULL; + } + + capabilities->topology = ast_stream_topology_clone(topology); + if (!capabilities->topology) { + sdp_state_capabilities_free(capabilities); + return NULL; + } + + if (AST_VECTOR_INIT(&capabilities->streams, + ast_stream_topology_get_count(topology))) { + sdp_state_capabilities_free(capabilities); + return NULL; + } + ast_sockaddr_setnull(&capabilities->connection_address); + + for (i = 0; i < ast_stream_topology_get_count(topology); ++i) { + struct sdp_state_stream *state_stream; + + state_stream = ast_calloc(1, sizeof(*state_stream)); + if (!state_stream) { + 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->instance = create_rtp(options, state_stream->type); + if (!state_stream->instance) { + sdp_state_stream_free(state_stream); + return NULL; + } + break; + case AST_MEDIA_TYPE_IMAGE: + state_stream->udptl = create_udptl(options); + if (!state_stream->udptl) { + sdp_state_stream_free(state_stream); + return NULL; + } + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; + } + + AST_VECTOR_APPEND(&capabilities->streams, state_stream); + } + + return capabilities; +} + +/*! + * \brief SDP state, the main structure used to keep track of SDP negotiation + * and settings. + * + * Most fields are pretty self-explanatory, but negotiated_capabilities and + * proposed_capabilities could use some further explanation. When an SDP + * state is allocated, a stream topology is provided that dictates the + * types of streams to offer in the resultant SDP. At the time the SDP + * 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. + * + * If we are the SDP answerer, then the incoming SDP offer is merged with our + * proposed capabilities to to create the negotiated capabilities. These negotiated + * capabilities are what we send in our SDP answer. + * + * 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 + * done so that the negotiated capabilities can be fallen back on if the proposed + * capabilities run into some sort of issue. + */ struct ast_sdp_state { - /*! Local capabilities, learned through configuration */ - struct sdp_state_capabilities local_capabilities; + /*! Current capabilities */ + struct sdp_state_capabilities *negotiated_capabilities; + /*! Proposed capabilities */ + struct sdp_state_capabilities *proposed_capabilities; /*! Remote capabilities, learned through remote SDP */ struct ast_stream_topology *remote_capabilities; - /*! Joint capabilities. The combined local and remote capabilities. */ - struct sdp_state_capabilities joint_capabilities; /*! Local SDP. Generated via the options and local capabilities. */ struct ast_sdp *local_sdp; - /*! Remote SDP. Received directly from a peer. */ - struct ast_sdp *remote_sdp; - /*! Joint SDP. The merged local and remote SDPs. */ - struct ast_sdp *joint_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; - /*! The current state machine state that we are in */ - enum ast_sdp_state_machine state; + /*! The role that we occupy in SDP negotiation */ + enum ast_sdp_role role; }; struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *streams, @@ -124,56 +355,39 @@ struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *streams, return NULL; } - if (ast_sdp_state_update_local_topology(sdp_state, streams)) { + sdp_state->proposed_capabilities = sdp_initialize_state_capabilities(streams, options); + if (!sdp_state->proposed_capabilities) { ast_sdp_state_free(sdp_state); return NULL; } - sdp_state->state = SDP_STATE_INITIAL; + sdp_state->role = SDP_ROLE_NOT_SET; return sdp_state; } -static void sdp_state_capabilities_free(struct sdp_state_capabilities *sdp_capabilities) -{ - int stream_index; - - for (stream_index = 0; stream_index < AST_VECTOR_SIZE(&sdp_capabilities->streams); stream_index++) { - struct sdp_state_stream *stream_state = AST_VECTOR_GET_ADDR(&sdp_capabilities->streams, stream_index); - enum ast_media_type type = ast_stream_get_type(ast_stream_topology_get_stream(sdp_capabilities->topology, stream_index)); - - if (type == AST_MEDIA_TYPE_AUDIO || type == AST_MEDIA_TYPE_VIDEO) { - ast_rtp_instance_destroy(stream_state->instance); - } - } - - ast_stream_topology_free(sdp_capabilities->topology); - AST_VECTOR_FREE(&sdp_capabilities->streams); -} - void ast_sdp_state_free(struct ast_sdp_state *sdp_state) { if (!sdp_state) { return; } - sdp_state_capabilities_free(&sdp_state->local_capabilities); + sdp_state_capabilities_free(sdp_state->negotiated_capabilities); + sdp_state_capabilities_free(sdp_state->proposed_capabilities); ast_stream_topology_free(sdp_state->remote_capabilities); - sdp_state_capabilities_free(&sdp_state->joint_capabilities); ast_sdp_free(sdp_state->local_sdp); - ast_sdp_free(sdp_state->remote_sdp); - ast_sdp_free(sdp_state->joint_sdp); ast_sdp_options_free(sdp_state->options); ast_sdp_translator_free(sdp_state->translator); + ast_free(sdp_state); } 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->local_capabilities.streams)) { + if (stream_index >= AST_VECTOR_SIZE(&sdp_state->proposed_capabilities->streams)) { return NULL; } - return AST_VECTOR_GET_ADDR(&sdp_state->local_capabilities.streams, stream_index); + return AST_VECTOR_GET(&sdp_state->proposed_capabilities->streams, stream_index); } struct ast_rtp_instance *ast_sdp_state_get_rtp_instance( @@ -182,6 +396,9 @@ struct ast_rtp_instance *ast_sdp_state_get_rtp_instance( struct sdp_state_stream *stream_state; ast_assert(sdp_state != NULL); + ast_assert(ast_stream_get_type(ast_stream_topology_get_stream(sdp_state->proposed_capabilities->topology, + stream_index)) == AST_MEDIA_TYPE_AUDIO || ast_stream_get_type(ast_stream_topology_get_stream( + sdp_state->proposed_capabilities->topology, stream_index)) == AST_MEDIA_TYPE_VIDEO); stream_state = sdp_state_get_stream(sdp_state, stream_index); if (!stream_state) { @@ -191,18 +408,34 @@ struct ast_rtp_instance *ast_sdp_state_get_rtp_instance( return stream_state->instance; } +struct ast_udptl *ast_sdp_state_get_udptl_instance( + const struct ast_sdp_state *sdp_state, int stream_index) +{ + struct sdp_state_stream *stream_state; + + ast_assert(sdp_state != NULL); + ast_assert(ast_stream_get_type(ast_stream_topology_get_stream(sdp_state->proposed_capabilities->topology, + stream_index)) == AST_MEDIA_TYPE_IMAGE); + + stream_state = sdp_state_get_stream(sdp_state, stream_index); + if (!stream_state || !stream_state->udptl) { + return NULL; + } + + return stream_state->udptl->instance; +} + const struct ast_sockaddr *ast_sdp_state_get_connection_address(const struct ast_sdp_state *sdp_state) { ast_assert(sdp_state != NULL); - return &sdp_state->local_capabilities.connection_address; + return &sdp_state->proposed_capabilities->connection_address; } 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; - enum ast_media_type type; ast_assert(sdp_state != NULL); ast_assert(address != NULL); @@ -218,20 +451,26 @@ int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_ return 0; } - type = ast_stream_get_type(ast_stream_topology_get_stream(sdp_state->local_capabilities.topology, - stream_index)); - - if (type == AST_MEDIA_TYPE_AUDIO || type == AST_MEDIA_TYPE_VIDEO) { + switch (ast_stream_get_type(ast_stream_topology_get_stream(sdp_state->proposed_capabilities->topology, + stream_index))) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: ast_rtp_instance_get_local_address(stream_state->instance, address); - } else { + break; + case AST_MEDIA_TYPE_IMAGE: + ast_udptl_get_us(stream_state->udptl->instance, address); + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: return -1; } /* If an explicit global connection address is set use it here for the IP part */ - if (!ast_sockaddr_isnull(&sdp_state->local_capabilities.connection_address)) { + if (!ast_sockaddr_isnull(&sdp_state->proposed_capabilities->connection_address)) { int port = ast_sockaddr_port(address); - ast_sockaddr_copy(address, &sdp_state->local_capabilities.connection_address); + ast_sockaddr_copy(address, &sdp_state->proposed_capabilities->connection_address); ast_sockaddr_set_port(address, port); } @@ -242,11 +481,12 @@ const struct ast_stream_topology *ast_sdp_state_get_joint_topology( const struct ast_sdp_state *sdp_state) { ast_assert(sdp_state != NULL); - if (sdp_state->state == SDP_STATE_NEGOTIATED) { - return sdp_state->joint_capabilities.topology; - } else { - return sdp_state->local_capabilities.topology; + + if (sdp_state->negotiated_capabilities) { + return sdp_state->negotiated_capabilities->topology; } + + return sdp_state->proposed_capabilities->topology; } const struct ast_stream_topology *ast_sdp_state_get_local_topology( @@ -254,7 +494,7 @@ const struct ast_stream_topology *ast_sdp_state_get_local_topology( { ast_assert(sdp_state != NULL); - return sdp_state->local_capabilities.topology; + return sdp_state->proposed_capabilities->topology; } const struct ast_sdp_options *ast_sdp_state_get_options( @@ -265,120 +505,522 @@ const struct ast_sdp_options *ast_sdp_state_get_options( return sdp_state->options; } -#if 0 -static int merge_sdps(struct ast_sdp_state *sdp_state) -{ - ast_assert(sdp_state->local_sdp != NULL); - ast_assert(sdp_state->remote_sdp != NULL); - /* XXX STUB */ - /* The goal of this function is to take - * sdp_state->local_sdp and sdp_state->remote_sdp - * and negotiate those into a joint SDP. This joint - * SDP should be stored in sdp_state->joint_sdp. After - * the joint SDP is created, the joint SDP should be - * used to create the joint topology. Finally, if necessary, - * the RTP session may need to be adjusted in some ways. For - * instance, if we previously opened three ports for three - * streams, but we negotiate down to two streams, then we - * can shut down the port for the third stream. Similarly, - * if we end up negotiating something like BUNDLE, then we may - * need to tell the RTP layer to close ports and to multiplex - * streams. - */ +/*! + * \brief Merge two streams into a joint stream. + * + * \param local Our local stream + * \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, + 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; + } - return 0; + if (!local) { + return joint_stream; + } + + 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); + + 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); + + ao2_ref(joint_cap, -1); + + return joint_stream; } -#endif -/* TODO - * This isn't set anywhere yet. +/*! + * \brief Get a local stream that corresponds with a remote stream. + * + * \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 + * \retval NULL No corresponding stream found + * \retval non-NULL The corresponding stream */ -/*! \brief Scheduler for RTCP purposes */ -static struct ast_sched_context *sched; +static int get_corresponding_index(const struct ast_stream_topology *local, + enum ast_media_type media_type, int *media_indices) +{ + int i; + int winner = -1; -/*! \brief Internal function which creates an RTP instance */ -static struct ast_rtp_instance *create_rtp(const struct ast_sdp_options *options, - enum ast_media_type media_type) + for (i = media_indices[media_type]; i < ast_stream_topology_get_count(local); ++i) { + struct ast_stream *candidate; + + candidate = ast_stream_topology_get_stream(local, i); + if (ast_stream_get_type(candidate) == media_type) { + winner = i; + break; + } + } + + media_indices[media_type] = i + 1; + return winner; +} + +/*! + * \brief Merge existing stream capabilities and a new topology into joint capabilities. + * + * 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 current + * 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 create_new 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 + * create_new parameter is true, this created stream is made a clone of the new stream, + * and a media instance is created. If the create_new parameter is not true, then the + * created stream has no formats set and no media instance is created for it. + * + * \param current Current capabilities of the SDP state (may be NULL) + * \param new_topology The new topology to base merged capabilities on + * \param options The options set on the SDP state + * \retval NULL An error occurred + * \retval non-NULL The merged capabilities + */ +static struct sdp_state_capabilities *merge_capabilities(const struct sdp_state_capabilities *current, + const struct ast_stream_topology *new_topology, const struct ast_sdp_options *options, int create_missing) +{ + struct sdp_state_capabilities *joint_capabilities; + struct ast_stream_topology *topology; + int media_indices[AST_MEDIA_TYPE_END] = {0}; + int i; + + ast_assert(current != NULL); + + joint_capabilities = ast_calloc(1, sizeof(*joint_capabilities)); + if (!joint_capabilities) { + return NULL; + } + + joint_capabilities->topology = ast_stream_topology_alloc(); + if (!joint_capabilities->topology) { + goto fail; + } + + AST_VECTOR_INIT(&joint_capabilities->streams, AST_VECTOR_SIZE(¤t->streams)); + ast_sockaddr_copy(&joint_capabilities->connection_address, ¤t->connection_address); + topology = current->topology; + + for (i = 0; i < ast_stream_topology_get_count(new_topology); ++i) { + enum ast_media_type new_stream_type; + struct ast_stream *new_stream; + struct ast_stream *current_stream; + struct ast_stream *joint_stream; + struct sdp_state_stream *current_state_stream; + struct sdp_state_stream *joint_state_stream; + int current_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); + + current_index = get_corresponding_index(topology, new_stream_type, media_indices); + + if (current_index >= 0) { + current_stream = ast_stream_topology_get_stream(topology, current_index); + joint_stream = merge_streams(current_stream, new_stream); + if (!joint_stream) { + goto fail; + } + + current_state_stream = AST_VECTOR_GET(¤t->streams, current_index); + joint_state_stream->type = current_state_stream->type; + + switch (joint_state_stream->type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + joint_state_stream->instance = ao2_bump(current_state_stream->instance); + break; + case AST_MEDIA_TYPE_IMAGE: + joint_state_stream->udptl = ao2_bump(current_state_stream->udptl); + joint_state_stream->t38_local_params = current_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(¤t_state_stream->connection_address)) { + ast_sockaddr_copy(&joint_state_stream->connection_address, ¤t_state_stream->connection_address); + } else { + ast_sockaddr_setnull(&joint_state_stream->connection_address); + } + joint_state_stream->locally_held = current_state_stream->locally_held; + } else if (create_missing) { + /* 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); + if (!joint_stream) { + goto fail; + } + + switch (new_stream_type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + joint_state_stream->instance = create_rtp(options, new_stream_type); + if (!joint_state_stream->instance) { + goto fail; + } + break; + case AST_MEDIA_TYPE_IMAGE: + joint_state_stream->udptl = create_udptl(options); + if (!joint_state_stream->udptl) { + goto fail; + } + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; + } + 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", new_stream_type); + if (!joint_stream) { + goto fail; + } + } + + ast_stream_topology_append_stream(joint_capabilities->topology, joint_stream); + AST_VECTOR_APPEND(&joint_capabilities->streams, joint_state_stream); + } + + return joint_capabilities; + +fail: + sdp_state_capabilities_free(joint_capabilities); + return NULL; +} + +/*! + * \brief Apply remote SDP's ICE information to our RTP session + * + * \param state The SDP state on which negotiation has taken place + * \param options The SDP options we support + * \param remote_sdp The SDP we most recently received + * \param remote_m_line The stream on which we are examining ICE candidates + */ +static void update_ice(const struct ast_sdp_state *state, struct ast_rtp_instance *rtp, const struct ast_sdp_options *options, + const struct ast_sdp *remote_sdp, const struct ast_sdp_m_line *remote_m_line) { - struct ast_rtp_instance *rtp; struct ast_rtp_engine_ice *ice; - struct ast_sockaddr temp_media_address; - static struct ast_sockaddr address_rtp; - struct ast_sockaddr *media_address = &address_rtp; + const struct ast_sdp_a_line *attr; + unsigned int attr_i; - if (options->bind_rtp_to_media_address && !ast_strlen_zero(options->media_address)) { - ast_sockaddr_parse(&temp_media_address, options->media_address, 0); - media_address = &temp_media_address; + /* If ICE support is not enabled or available exit early */ + if (ast_sdp_options_get_ice(options) != AST_SDP_ICE_ENABLED_STANDARD || !(ice = ast_rtp_instance_get_ice(rtp))) { + return; + } + + attr = ast_sdp_m_find_attribute(remote_m_line, "ice-ufrag", -1); + if (!attr) { + attr = ast_sdp_find_attribute(remote_sdp, "ice-ufrag", -1); + } + if (attr) { + ice->set_authentication(rtp, attr->value, NULL); } else { - if (ast_check_ipv6()) { - ast_sockaddr_parse(&address_rtp, "::", 0); + return; + } + + attr = ast_sdp_m_find_attribute(remote_m_line, "ice-pwd", -1); + if (!attr) { + attr = ast_sdp_find_attribute(remote_sdp, "ice-pwd", -1); + } + if (attr) { + ice->set_authentication(rtp, NULL, attr->value); + } else { + return; + } + + if (ast_sdp_m_find_attribute(remote_m_line, "ice-lite", -1)) { + ice->ice_lite(rtp); + } + + /* Find all of the candidates */ + for (attr_i = 0; attr_i < ast_sdp_m_get_a_count(remote_m_line); ++attr_i) { + char foundation[32]; + char transport[32]; + char address[INET6_ADDRSTRLEN + 1]; + char cand_type[6]; + char relay_address[INET6_ADDRSTRLEN + 1] = ""; + unsigned int port; + unsigned int relay_port = 0; + struct ast_rtp_engine_ice_candidate candidate = { 0, }; + + attr = ast_sdp_m_get_a(remote_m_line, attr_i); + + /* If this is not a candidate line skip it */ + if (strcmp(attr->name, "candidate")) { + continue; + } + + if (sscanf(attr->value, "%31s %30u %31s %30u %46s %30u typ %5s %*s %23s %*s %30u", + foundation, &candidate.id, transport, (unsigned *)&candidate.priority, address, + &port, cand_type, relay_address, &relay_port) < 7) { + /* Candidate did not parse properly */ + continue; + } + + if (ast_sdp_options_get_rtcp_mux(options) + && ast_sdp_m_find_attribute(remote_m_line, "rtcp-mux", -1) + && candidate.id > 1) { + /* Remote side may have offered RTP and RTCP candidates. However, if we're using RTCP MUX, + * then we should ignore RTCP candidates. + */ + continue; + } + + candidate.foundation = foundation; + candidate.transport = transport; + + ast_sockaddr_parse(&candidate.address, address, PARSE_PORT_FORBID); + ast_sockaddr_set_port(&candidate.address, port); + + if (!strcasecmp(cand_type, "host")) { + candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_HOST; + } else if (!strcasecmp(cand_type, "srflx")) { + candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_SRFLX; + } else if (!strcasecmp(cand_type, "relay")) { + candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_RELAYED; } else { - ast_sockaddr_parse(&address_rtp, "0.0.0.0", 0); + continue; + } + + if (!ast_strlen_zero(relay_address)) { + ast_sockaddr_parse(&candidate.relay_address, relay_address, PARSE_PORT_FORBID); + } + + if (relay_port) { + ast_sockaddr_set_port(&candidate.relay_address, relay_port); } + + ice->add_remote_candidate(rtp, &candidate); } - if (!(rtp = ast_rtp_instance_new(options->rtp_engine, sched, media_address, NULL))) { - ast_log(LOG_ERROR, "Unable to create RTP instance using RTP engine '%s'\n", - options->rtp_engine); - return NULL; + if (state->role == SDP_ROLE_OFFERER) { + ice->set_role(rtp, AST_RTP_ICE_ROLE_CONTROLLING); + } else { + ice->set_role(rtp, AST_RTP_ICE_ROLE_CONTROLLED); } - ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_RTCP, 1); - ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_NAT, options->rtp_symmetric); + ice->start(rtp); +} - if (options->ice == AST_SDP_ICE_DISABLED && (ice = ast_rtp_instance_get_ice(rtp))) { - ice->stop(rtp); +/*! + * \brief Update RTP instances based on merged SDPs + * + * RTP instances, when first allocated, cannot make assumptions about what the other + * side supports and thus has to go with some default behaviors. This function gets + * called after we know both what we support and what the remote endpoint supports. + * This way, we can update the RTP instance to reflect what is supported by both + * sides. + * + * \param state The SDP state in which SDPs have been negotiated + * \param rtp The RTP instance that is being updated + * \param options Our locally-supported SDP options + * \param remote_sdp The SDP we most recently received + * \param remote_m_line The remote SDP stream that corresponds to the RTP instance we are modifying + */ +static void update_rtp_after_merge(const struct ast_sdp_state *state, struct ast_rtp_instance *rtp, + const struct ast_sdp_options *options, + const struct ast_sdp *remote_sdp, + const struct ast_sdp_m_line *remote_m_line) +{ + if (ast_sdp_options_get_rtcp_mux(options) && ast_sdp_m_find_attribute(remote_m_line, "rtcp-mux", -1)) { + ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_MUX); + } else { + ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_STANDARD); } - if (options->telephone_event) { - ast_rtp_instance_dtmf_mode_set(rtp, AST_RTP_DTMF_MODE_RFC2833); - ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_DTMF, 1); + if (ast_sdp_options_get_ice(options) == AST_SDP_ICE_ENABLED_STANDARD) { + update_ice(state, rtp, options, remote_sdp, remote_m_line); } +} - if (media_type == AST_MEDIA_TYPE_AUDIO && - (options->tos_audio || options->cos_audio)) { - ast_rtp_instance_set_qos(rtp, options->tos_audio, - options->cos_audio, "SIP RTP Audio"); - } else if (media_type == AST_MEDIA_TYPE_VIDEO && - (options->tos_video || options->cos_video)) { - ast_rtp_instance_set_qos(rtp, options->tos_video, - options->cos_video, "SIP RTP Video"); +/*! + * \brief Update UDPTL instances based on merged SDPs + * + * UDPTL instances, when first allocated, cannot make assumptions about what the other + * side supports and thus has to go with some default behaviors. This function gets + * called after we know both what we support and what the remote endpoint supports. + * This way, we can update the UDPTL instance to reflect what is supported by both + * sides. + * + * \param state The SDP state in which SDPs have been negotiated + * \param udptl The UDPTL instance that is being updated + * \param options Our locally-supported SDP options + * \param remote_sdp The SDP we most recently received + * \param remote_m_line The remote SDP stream that corresponds to the RTP instance we are modifying + */ +static void update_udptl_after_merge(const struct ast_sdp_state *state, struct sdp_state_udptl *udptl, + const struct ast_sdp_options *options, + const struct ast_sdp *remote_sdp, + const struct ast_sdp_m_line *remote_m_line) +{ + struct ast_sdp_a_line *a_line; + struct ast_sdp_c_line *c_line; + unsigned int fax_max_datagram; + struct ast_sockaddr *addrs; + + 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); + } + if (a_line && !ast_sdp_options_get_udptl_far_max_datagram(options) && + (sscanf(a_line->value, "%30u", &fax_max_datagram) == 1)) { + ast_udptl_set_far_max_datagram(udptl->instance, fax_max_datagram); } - ast_rtp_instance_set_last_rx(rtp, time(NULL)); + a_line = ast_sdp_m_find_attribute(remote_m_line, "t38faxudpec", -1); + if (a_line) { + if (!strcasecmp(a_line->value, "t38UDPRedundancy")) { + ast_udptl_set_error_correction_scheme(udptl->instance, UDPTL_ERROR_CORRECTION_REDUNDANCY); + } else if (!strcasecmp(a_line->value, "t38UDPFEC")) { + ast_udptl_set_error_correction_scheme(udptl->instance, UDPTL_ERROR_CORRECTION_FEC); + } else { + ast_udptl_set_error_correction_scheme(udptl->instance, UDPTL_ERROR_CORRECTION_NONE); + } + } - return rtp; + c_line = remote_sdp->c_line; + if (remote_m_line->c_line) { + c_line = remote_m_line->c_line; + } + + if (ast_sockaddr_resolve(&addrs, c_line->address, PARSE_PORT_FORBID, AST_AF_UNSPEC) > 0) { + ast_sockaddr_set_port(addrs, remote_m_line->port); + ast_udptl_set_peer(udptl->instance, addrs); + ast_free(addrs); + } } -static int sdp_state_setup_local_streams(struct ast_sdp_state *sdp_state) +static void set_negotiated_capabilities(struct ast_sdp_state *sdp_state, + struct sdp_state_capabilities *new_capabilities) { - int stream_index; + struct sdp_state_capabilities *old_capabilities = sdp_state->negotiated_capabilities; - for (stream_index = 0; stream_index < AST_VECTOR_SIZE(&sdp_state->local_capabilities.streams); stream_index++) { - struct sdp_state_stream *stream_state_local = AST_VECTOR_GET_ADDR(&sdp_state->local_capabilities.streams, stream_index); - struct sdp_state_stream *stream_state_joint = NULL; - enum ast_media_type type_local = ast_stream_get_type(ast_stream_topology_get_stream(sdp_state->local_capabilities.topology, stream_index)); - enum ast_media_type type_joint = AST_MEDIA_TYPE_UNKNOWN; + sdp_state->negotiated_capabilities = new_capabilities; + sdp_state_capabilities_free(old_capabilities); +} - if (stream_index < AST_VECTOR_SIZE(&sdp_state->joint_capabilities.streams)) { - stream_state_joint = AST_VECTOR_GET_ADDR(&sdp_state->joint_capabilities.streams, stream_index); - type_joint = ast_stream_get_type(ast_stream_topology_get_stream(sdp_state->joint_capabilities.topology, stream_index)); - } +static void set_proposed_capabilities(struct ast_sdp_state *sdp_state, + struct sdp_state_capabilities *new_capabilities) +{ + struct sdp_state_capabilities *old_capabilities = sdp_state->proposed_capabilities; - /* If we can reuse an existing media stream then do so */ - if (type_local == type_joint) { - if (type_local == AST_MEDIA_TYPE_AUDIO || type_local == AST_MEDIA_TYPE_VIDEO) { - stream_state_local->instance = ao2_bump(stream_state_joint->instance); - continue; - } - } + sdp_state->proposed_capabilities = new_capabilities; + sdp_state_capabilities_free(old_capabilities); +} - if (type_local == AST_MEDIA_TYPE_AUDIO || type_local == AST_MEDIA_TYPE_VIDEO) { - /* We need to create a new RTP instance */ - stream_state_local->instance = create_rtp(sdp_state->options, type_local); - if (!stream_state_local->instance) { - return -1; - } +static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_state, + const struct sdp_state_capabilities *capabilities); + +/*! + * \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 + * 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 + */ +static int merge_sdps(struct ast_sdp_state *sdp_state, + const struct ast_sdp *remote_sdp) +{ + struct sdp_state_capabilities *joint_capabilities; + int i; + + sdp_state->remote_capabilities = ast_get_topology_from_sdp(remote_sdp); + if (!sdp_state->remote_capabilities) { + return -1; + } + + joint_capabilities = merge_capabilities(sdp_state->proposed_capabilities, + sdp_state->remote_capabilities, sdp_state->options, 0); + 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; + } + + 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); + + 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->instance, 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; } } @@ -389,11 +1031,10 @@ const struct ast_sdp *ast_sdp_state_get_local_sdp(struct ast_sdp_state *sdp_stat { ast_assert(sdp_state != NULL); - if (!sdp_state->local_sdp) { - if (sdp_state_setup_local_streams(sdp_state)) { - return NULL; - } - sdp_state->local_sdp = ast_sdp_create_from_state(sdp_state); + if (sdp_state->role == SDP_ROLE_NOT_SET) { + ast_assert(sdp_state->local_sdp == NULL); + sdp_state->role = SDP_ROLE_OFFERER; + sdp_state->local_sdp = sdp_create_from_state(sdp_state, sdp_state->proposed_capabilities); } return sdp_state->local_sdp; @@ -410,11 +1051,15 @@ const void *ast_sdp_state_get_local_sdp_impl(struct ast_sdp_state *sdp_state) return ast_sdp_translator_from_sdp(sdp_state->translator, sdp); } -void ast_sdp_state_set_remote_sdp(struct ast_sdp_state *sdp_state, struct ast_sdp *sdp) +void ast_sdp_state_set_remote_sdp(struct ast_sdp_state *sdp_state, const struct ast_sdp *sdp) { ast_assert(sdp_state != NULL); - sdp_state->remote_sdp = sdp; + if (sdp_state->role == SDP_ROLE_NOT_SET) { + sdp_state->role = SDP_ROLE_ANSWERER; + } + + merge_sdps(sdp_state, sdp); } int ast_sdp_state_set_remote_sdp_from_impl(struct ast_sdp_state *sdp_state, void *remote) @@ -427,8 +1072,7 @@ int ast_sdp_state_set_remote_sdp_from_impl(struct ast_sdp_state *sdp_state, void if (!sdp) { return -1; } - - sdp_state->remote_sdp = sdp; + ast_sdp_state_set_remote_sdp(sdp_state, sdp); return 0; } @@ -440,37 +1084,27 @@ int ast_sdp_state_reset(struct ast_sdp_state *sdp_state) ast_sdp_free(sdp_state->local_sdp); sdp_state->local_sdp = NULL; - ast_sdp_free(sdp_state->remote_sdp); - sdp_state->remote_sdp = NULL; - - ast_sdp_free(sdp_state->joint_sdp); - sdp_state->joint_sdp = NULL; - ast_stream_topology_free(sdp_state->remote_capabilities); sdp_state->remote_capabilities = NULL; - ast_stream_topology_free(sdp_state->joint_capabilities.topology); - sdp_state->joint_capabilities.topology = NULL; + set_proposed_capabilities(sdp_state, NULL); - sdp_state->state = SDP_STATE_INITIAL; + sdp_state->role = SDP_ROLE_NOT_SET; return 0; } int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *streams) { + struct sdp_state_capabilities *capabilities; ast_assert(sdp_state != NULL); ast_assert(streams != NULL); - sdp_state_capabilities_free(&sdp_state->local_capabilities); - sdp_state->local_capabilities.topology = ast_stream_topology_clone(streams); - if (!sdp_state->local_capabilities.topology) { - return -1; - } - - if (AST_VECTOR_INIT(&sdp_state->local_capabilities.streams, ast_stream_topology_get_count(streams))) { + capabilities = merge_capabilities(sdp_state->proposed_capabilities, streams, sdp_state->options, 1); + if (!capabilities) { return -1; } + set_proposed_capabilities(sdp_state, capabilities); return 0; } @@ -480,9 +1114,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->local_capabilities.connection_address); + ast_sockaddr_setnull(&sdp_state->proposed_capabilities->connection_address); } else { - ast_sockaddr_copy(&sdp_state->local_capabilities.connection_address, address); + ast_sockaddr_copy(&sdp_state->proposed_capabilities->connection_address, address); } } @@ -533,3 +1167,402 @@ unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_stat return stream_state->locally_held; } + +void ast_sdp_state_set_t38_parameters(struct ast_sdp_state *sdp_state, + int stream_index, struct ast_control_t38_parameters *params) +{ + struct sdp_state_stream *stream_state; + ast_assert(sdp_state != NULL && params != NULL); + + stream_state = sdp_state_get_stream(sdp_state, stream_index); + if (!stream_state) { + return; + } + + stream_state->t38_local_params = *params; +} + +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) +{ + struct ast_stream *stream; + 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; + struct ast_sdp_a_line *a_line; + + stream = ast_stream_topology_get_stream(capabilities->topology, stream_index); + rtp = AST_VECTOR_GET(&capabilities->streams, stream_index)->instance; + + ast_assert(sdp && options && stream); + + media_type = ast_stream_get_type(stream); + if (rtp) { + if (ast_sdp_state_get_stream_connection_address(sdp_state, 0, &address_rtp)) { + return -1; + } + } else { + ast_sockaddr_setnull(&address_rtp); + } + + 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, 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); + } + + ao2_ref(format, -1); + } + + if (rtp && 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 (ast_sdp_m_add_format(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; +} + +/*! \brief Get Max T.38 Transmission rate from T38 capabilities */ +static unsigned int t38_get_rate(enum ast_control_t38_rate rate) +{ + switch (rate) { + case AST_T38_RATE_2400: + return 2400; + case AST_T38_RATE_4800: + return 4800; + case AST_T38_RATE_7200: + return 7200; + case AST_T38_RATE_9600: + return 9600; + case AST_T38_RATE_12000: + return 12000; + case AST_T38_RATE_14400: + return 14400; + default: + return 0; + } +} + +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) +{ + struct ast_stream *stream; + struct ast_sdp_m_line *m_line; + struct ast_sdp_payload *payload; + char tmp[64]; + struct ast_sockaddr address_udptl; + struct sdp_state_udptl *udptl; + struct ast_sdp_a_line *a_line; + struct sdp_state_stream *stream_state; + + stream = ast_stream_topology_get_stream(capabilities->topology, stream_index); + udptl = AST_VECTOR_GET(&capabilities->streams, stream_index)->udptl; + + ast_assert(sdp && options && stream); + + if (udptl) { + if (ast_sdp_state_get_stream_connection_address(sdp_state, 0, &address_udptl)) { + return -1; + } + } else { + ast_sockaddr_setnull(&address_udptl); + } + + m_line = ast_sdp_m_alloc( + ast_codec_media_type2str(ast_stream_get_type(stream)), + ast_sockaddr_port(&address_udptl), 1, "udptl", NULL); + if (!m_line) { + return -1; + } + + payload = ast_sdp_payload_alloc("t38"); + if (!payload || ast_sdp_m_add_payload(m_line, payload)) { + ast_sdp_payload_free(payload); + ast_sdp_m_free(m_line); + return -1; + } + + 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; + } + + 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_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.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; + } + } + + 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; + } + + 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; + } + + if (ast_sdp_add_m(sdp, m_line)) { + ast_sdp_m_free(m_line); + return -1; + } + + return 0; +} + +/*! + * \brief Create an SDP based on current SDP state + * + * \param sdp_state The current SDP state + * \retval NULL Failed to create SDP + * \retval non-NULL Newly-created SDP + */ +static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_state, + const struct sdp_state_capabilities *capabilities) +{ + struct ast_sdp *sdp = NULL; + struct ast_stream_topology *topology; + const struct ast_sdp_options *options; + 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; + int stream_count; + + options = ast_sdp_state_get_options(sdp_state); + topology = capabilities->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; + } + + 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)) { + goto error; + } + break; + case AST_MEDIA_TYPE_IMAGE: + if (sdp_add_m_from_udptl_stream(sdp, sdp_state, options, capabilities, stream_num)) { + goto error; + } + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; + } + } + + 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; +} + diff --git a/res/res_hep.c b/res/res_hep.c index 41a558141..25b4d13b1 100644 --- a/res/res_hep.c +++ b/res/res_hep.c @@ -69,7 +69,7 @@ </enumlist> </description> </configOption> - <configOption name="capture_address" default="192.168.1.1:9061"> + <configOption name="capture_address"> <synopsis>The address and port of the Homer server to send packets to.</synopsis> </configOption> <configOption name="capture_password"> @@ -96,8 +96,6 @@ #include <netinet/udp.h> #include <netinet/ip6.h> -#define DEFAULT_HEP_SERVER "" - /*! Generic vendor ID. Used for HEPv3 standard packets */ #define GENERIC_VENDOR_ID 0x0000 @@ -280,11 +278,13 @@ static AO2_GLOBAL_OBJ_STATIC(global_data); static struct ast_taskprocessor *hep_queue_tp; static void *module_config_alloc(void); +static int hepv3_config_pre_apply(void); static void hepv3_config_post_apply(void); /*! \brief Register information about the configs being processed by this module */ CONFIG_INFO_STANDARD(cfg_info, global_config, module_config_alloc, .files = ACO_FILES(&hepv3_conf), + .pre_apply_config = hepv3_config_pre_apply, .post_apply_config = hepv3_config_post_apply, ); @@ -377,6 +377,8 @@ static struct hepv3_runtime_data *hepv3_data_alloc(struct hepv3_global_config *c return NULL; } + data->sockfd = -1; + if (!ast_sockaddr_parse(&data->remote_addr, config->capture_address, PARSE_PORT_REQUIRE)) { ast_log(AST_LOG_WARNING, "Failed to create address from %s\n", config->capture_address); ao2_ref(data, -1); @@ -594,11 +596,33 @@ int hepv3_send_packet(struct hepv3_capture_info *capture_info) } /*! + * \brief Pre-apply callback for the config framework. + * + * This validates that required fields exist and are populated. + */ +static int hepv3_config_pre_apply(void) +{ + struct module_config *config = aco_pending_config(&cfg_info); + + if (!config->general->enabled) { + /* If we're not enabled, we don't care about anything else */ + return 0; + } + + if (ast_strlen_zero(config->general->capture_address)) { + ast_log(AST_LOG_ERROR, "Missing required configuration option 'capture_address'\n"); + return -1; + } + + return 0; +} + +/*! * \brief Post-apply callback for the config framework. * * This will create the run-time information from the supplied * configuration. -*/ + */ static void hepv3_config_post_apply(void) { RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(global_config), ao2_cleanup); @@ -653,7 +677,7 @@ static int load_module(void) } aco_option_register(&cfg_info, "enabled", ACO_EXACT, global_options, "yes", OPT_BOOL_T, 1, FLDSET(struct hepv3_global_config, enabled)); - aco_option_register(&cfg_info, "capture_address", ACO_EXACT, global_options, DEFAULT_HEP_SERVER, OPT_STRINGFIELD_T, 0, STRFLDSET(struct hepv3_global_config, capture_address)); + aco_option_register(&cfg_info, "capture_address", ACO_EXACT, global_options, "", OPT_STRINGFIELD_T, 1, STRFLDSET(struct hepv3_global_config, capture_address)); aco_option_register(&cfg_info, "capture_password", ACO_EXACT, global_options, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct hepv3_global_config, capture_password)); aco_option_register(&cfg_info, "capture_id", ACO_EXACT, global_options, "0", OPT_UINT_T, 0, STRFLDSET(struct hepv3_global_config, capture_id)); aco_option_register_custom(&cfg_info, "uuid_type", ACO_EXACT, global_options, "call-id", uuid_type_handler, 0); diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c index dfa6957c7..97e365c10 100644 --- a/res/res_pjsip_sdp_rtp.c +++ b/res/res_pjsip_sdp_rtp.c @@ -453,6 +453,7 @@ static int set_caps(struct ast_sip_session *session, static pjmedia_sdp_attr* generate_rtpmap_attr(struct ast_sip_session *session, pjmedia_sdp_media *media, pj_pool_t *pool, int rtp_code, int asterisk_format, struct ast_format *format, int code) { + extern pj_bool_t pjsip_use_compact_form; pjmedia_sdp_rtpmap rtpmap; pjmedia_sdp_attr *attr = NULL; char tmp[64]; @@ -461,6 +462,11 @@ static pjmedia_sdp_attr* generate_rtpmap_attr(struct ast_sip_session *session, p snprintf(tmp, sizeof(tmp), "%d", rtp_code); pj_strdup2(pool, &media->desc.fmt[media->desc.fmt_count++], tmp); + + if (rtp_code <= AST_RTP_PT_LAST_STATIC && pjsip_use_compact_form) { + return NULL; + } + rtpmap.pt = media->desc.fmt[media->desc.fmt_count - 1]; rtpmap.clock_rate = ast_rtp_lookup_sample_rate2(asterisk_format, format, code); pj_strdup2(pool, &rtpmap.enc_name, ast_rtp_lookup_mime_subtype2(asterisk_format, format, code, options)); @@ -1260,11 +1266,9 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as continue; } - if (!(attr = generate_rtpmap_attr(session, media, pool, rtp_code, 1, format, 0))) { - ao2_ref(format, -1); - continue; + if ((attr = generate_rtpmap_attr(session, media, pool, rtp_code, 1, format, 0))) { + media->attr[media->attr_count++] = attr; } - media->attr[media->attr_count++] = attr; if ((attr = generate_fmtp_attr(pool, format, rtp_code))) { media->attr[media->attr_count++] = attr; @@ -1293,12 +1297,10 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as continue; } - if (!(attr = generate_rtpmap_attr(session, media, pool, rtp_code, 0, NULL, index))) { - continue; + if ((attr = generate_rtpmap_attr(session, media, pool, rtp_code, 0, NULL, index))) { + media->attr[media->attr_count++] = attr; } - media->attr[media->attr_count++] = attr; - if (index == AST_RTP_DTMF) { snprintf(tmp, sizeof(tmp), "%d 0-16", rtp_code); attr = pjmedia_sdp_attr_create(pool, "fmtp", pj_cstr(&stmp, tmp)); diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c index 560b3903d..3034652a5 100644 --- a/res/res_pjsip_session.c +++ b/res/res_pjsip_session.c @@ -1328,9 +1328,7 @@ static void session_destructor(void *obj) ao2_cleanup(session->req_caps); ao2_cleanup(session->direct_media_cap); - if (session->dsp) { - ast_dsp_free(session->dsp); - } + ast_dsp_free(session->dsp); if (session->inv_session) { pjsip_dlg_dec_session(session->inv_session->dlg, &session_module); @@ -1400,6 +1398,7 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint, struct ast_sip_contact *contact, pjsip_inv_session *inv_session, pjsip_rx_data *rdata) { RAII_VAR(struct ast_sip_session *, session, NULL, ao2_cleanup); + struct ast_sip_session *ret_session; struct ast_sip_session_supplement *iter; int dsp_features = 0; @@ -1407,12 +1406,39 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint, if (!session) { return NULL; } + AST_LIST_HEAD_INIT(&session->supplements); + AST_LIST_HEAD_INIT_NOLOCK(&session->delayed_requests); + ast_party_id_init(&session->id); + + session->direct_media_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!session->direct_media_cap) { + return NULL; + } + session->req_caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!session->req_caps) { + return NULL; + } session->datastores = ao2_container_alloc(DATASTORE_BUCKETS, datastore_hash, datastore_cmp); if (!session->datastores) { return NULL; } + if (endpoint->dtmf == AST_SIP_DTMF_INBAND || endpoint->dtmf == AST_SIP_DTMF_AUTO) { + dsp_features |= DSP_FEATURE_DIGIT_DETECT; + } + if (endpoint->faxdetect) { + dsp_features |= DSP_FEATURE_FAX_DETECT; + } + if (dsp_features) { + session->dsp = ast_dsp_new(); + if (!session->dsp) { + return NULL; + } + + ast_dsp_set_features(session->dsp, dsp_features); + } + session->endpoint = ao2_bump(endpoint); session->media = ao2_container_alloc(MEDIA_BUCKETS, session_media_hash, session_media_cmp); @@ -1449,30 +1475,6 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint, inv_session->mod_data[session_module.id] = ao2_bump(session); session->contact = ao2_bump(contact); session->inv_session = inv_session; - session->req_caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); - if (!session->req_caps) { - /* Release the ref held by session->inv_session */ - ao2_ref(session, -1); - return NULL; - } - - if ((endpoint->dtmf == AST_SIP_DTMF_INBAND) || (endpoint->dtmf == AST_SIP_DTMF_AUTO)) { - dsp_features |= DSP_FEATURE_DIGIT_DETECT; - } - - if (endpoint->faxdetect) { - dsp_features |= DSP_FEATURE_FAX_DETECT; - } - - if (dsp_features) { - if (!(session->dsp = ast_dsp_new())) { - /* Release the ref held by session->inv_session */ - ao2_ref(session, -1); - return NULL; - } - - ast_dsp_set_features(session->dsp, dsp_features); - } if (add_supplements(session)) { /* Release the ref held by session->inv_session */ @@ -1484,11 +1486,11 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint, iter->session_begin(session); } } - session->direct_media_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); - AST_LIST_HEAD_INIT_NOLOCK(&session->delayed_requests); - ast_party_id_init(&session->id); - ao2_ref(session, +1); - return session; + + /* Avoid unnecessary ref manipulation to return a session */ + ret_session = session; + session = NULL; + return ret_session; } /*! \brief struct controlling the suspension of the session's serializer. */ @@ -1704,6 +1706,7 @@ struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint pjsip_dialog *dlg; struct pjsip_inv_session *inv_session; RAII_VAR(struct ast_sip_session *, session, NULL, ao2_cleanup); + struct ast_sip_session *ret_session; /* If no location has been provided use the AOR list from the endpoint itself */ if (location || !contact) { @@ -1760,14 +1763,17 @@ struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint if (ast_format_cap_count(req_caps)) { /* get joint caps between req_caps and endpoint caps */ struct ast_format_cap *joint_caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); - ast_format_cap_get_compatible(req_caps, session->endpoint->media.codecs, joint_caps); + + ast_format_cap_get_compatible(req_caps, endpoint->media.codecs, joint_caps); /* if joint caps */ if (ast_format_cap_count(joint_caps)) { /* copy endpoint caps into session->req_caps */ - ast_format_cap_append_from_cap(session->req_caps, session->endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(session->req_caps, + endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN); /* replace instances of joint caps equivalents in session->req_caps */ - ast_format_cap_replace_from_cap(session->req_caps, joint_caps, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_replace_from_cap(session->req_caps, joint_caps, + AST_MEDIA_TYPE_UNKNOWN); } ao2_cleanup(joint_caps); } @@ -1781,8 +1787,10 @@ struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint return NULL; } - ao2_ref(session, +1); - return session; + /* Avoid unnecessary ref manipulation to return a session */ + ret_session = session; + session = NULL; + return ret_session; } void ast_sip_session_terminate(struct ast_sip_session *session, int response) @@ -2142,12 +2150,29 @@ static int new_invite(void *data) goto end; }; - if ((sdp_info = pjsip_rdata_get_sdp_info(invite->rdata)) && (sdp_info->sdp_err == PJ_SUCCESS) && sdp_info->sdp) { + pjsip_timer_setting_default(&timer); + timer.min_se = invite->session->endpoint->extensions.timer.min_se; + timer.sess_expires = invite->session->endpoint->extensions.timer.sess_expires; + pjsip_timer_init_session(invite->session->inv_session, &timer); + + /* + * At this point, we've verified what we can that won't take awhile, + * so let's go ahead and send a 100 Trying out to stop any + * retransmissions. + */ + if (pjsip_inv_initial_answer(invite->session->inv_session, invite->rdata, 100, NULL, NULL, &tdata) != PJ_SUCCESS) { + pjsip_inv_terminate(invite->session->inv_session, 500, PJ_TRUE); + goto end; + } + ast_sip_session_send_response(invite->session, tdata); + + sdp_info = pjsip_rdata_get_sdp_info(invite->rdata); + if (sdp_info && (sdp_info->sdp_err == PJ_SUCCESS) && sdp_info->sdp) { if (handle_incoming_sdp(invite->session, sdp_info->sdp)) { - if (pjsip_inv_initial_answer(invite->session->inv_session, invite->rdata, 488, NULL, NULL, &tdata) == PJ_SUCCESS) { + tdata = NULL; + if (pjsip_inv_end_session(invite->session->inv_session, 488, NULL, &tdata) == PJ_SUCCESS + && tdata) { ast_sip_session_send_response(invite->session, tdata); - } else { - pjsip_inv_terminate(invite->session->inv_session, 488, PJ_TRUE); } goto end; } @@ -2160,33 +2185,21 @@ static int new_invite(void *data) /* If we were unable to create a local SDP terminate the session early, it won't go anywhere */ if (!local) { - if (pjsip_inv_initial_answer(invite->session->inv_session, invite->rdata, 500, NULL, NULL, &tdata) == PJ_SUCCESS) { + tdata = NULL; + if (pjsip_inv_end_session(invite->session->inv_session, 500, NULL, &tdata) == PJ_SUCCESS + && tdata) { ast_sip_session_send_response(invite->session, tdata); - } else { - pjsip_inv_terminate(invite->session->inv_session, 500, PJ_TRUE); } goto end; - } else { - pjsip_inv_set_local_sdp(invite->session->inv_session, local); - pjmedia_sdp_neg_set_prefer_remote_codec_order(invite->session->inv_session->neg, PJ_FALSE); -#ifdef PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS - if (!invite->session->endpoint->preferred_codec_only) { - pjmedia_sdp_neg_set_answer_multiple_codecs(invite->session->inv_session->neg, PJ_TRUE); - } -#endif } - pjsip_timer_setting_default(&timer); - timer.min_se = invite->session->endpoint->extensions.timer.min_se; - timer.sess_expires = invite->session->endpoint->extensions.timer.sess_expires; - pjsip_timer_init_session(invite->session->inv_session, &timer); - - /* At this point, we've verified what we can, so let's go ahead and send a 100 Trying out */ - if (pjsip_inv_initial_answer(invite->session->inv_session, invite->rdata, 100, NULL, NULL, &tdata) != PJ_SUCCESS) { - pjsip_inv_terminate(invite->session->inv_session, 500, PJ_TRUE); - goto end; + pjsip_inv_set_local_sdp(invite->session->inv_session, local); + pjmedia_sdp_neg_set_prefer_remote_codec_order(invite->session->inv_session->neg, PJ_FALSE); +#ifdef PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS + if (!invite->session->endpoint->preferred_codec_only) { + pjmedia_sdp_neg_set_answer_multiple_codecs(invite->session->inv_session->neg, PJ_TRUE); } - ast_sip_session_send_response(invite->session, tdata); +#endif handle_incoming_request(invite->session, invite->rdata); diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c index e2638320c..d0d795939 100644 --- a/res/res_rtp_asterisk.c +++ b/res/res_rtp_asterisk.c @@ -1721,8 +1721,10 @@ static void ast_rtp_dtls_stop(struct ast_rtp_instance *instance) dtls_srtp_stop_timeout_timer(instance, rtp, 1); ao2_lock(instance); - if (rtp->rtcp->dtls.ssl && (rtp->rtcp->dtls.ssl != ssl)) { - SSL_free(rtp->rtcp->dtls.ssl); + if (rtp->rtcp->dtls.ssl) { + if (rtp->rtcp->dtls.ssl != ssl) { + SSL_free(rtp->rtcp->dtls.ssl); + } rtp->rtcp->dtls.ssl = NULL; } } @@ -5445,14 +5447,14 @@ static void ast_rtp_prop_set(struct ast_rtp_instance *instance, enum ast_rtp_pro * to activating RTP. It is not until RTP is activated that timers start for RTCP * transmission */ - if (rtp->rtcp->s > -1) { + if (rtp->rtcp->s > -1 && rtp->rtcp->s != rtp->s) { close(rtp->rtcp->s); } rtp->rtcp->s = rtp->s; ast_rtp_instance_get_remote_address(instance, &addr); ast_sockaddr_copy(&rtp->rtcp->them, &addr); #ifdef HAVE_OPENSSL_SRTP - if (rtp->rtcp->dtls.ssl) { + if (rtp->rtcp->dtls.ssl && rtp->rtcp->dtls.ssl != rtp->dtls.ssl) { SSL_free(rtp->rtcp->dtls.ssl); } rtp->rtcp->dtls.ssl = rtp->dtls.ssl; @@ -5460,7 +5462,6 @@ static void ast_rtp_prop_set(struct ast_rtp_instance *instance, enum ast_rtp_pro } ast_debug(1, "Setup RTCP on RTP instance '%p'\n", instance); - return; } else { if (rtp->rtcp) { if (rtp->rtcp->schedid > -1) { @@ -5481,6 +5482,10 @@ static void ast_rtp_prop_set(struct ast_rtp_instance *instance, enum ast_rtp_pro close(rtp->rtcp->s); } #ifdef HAVE_OPENSSL_SRTP + ao2_unlock(instance); + dtls_srtp_stop_timeout_timer(instance, rtp, 1); + ao2_lock(instance); + if (rtp->rtcp->dtls.ssl && rtp->rtcp->dtls.ssl != rtp->dtls.ssl) { SSL_free(rtp->rtcp->dtls.ssl); } @@ -5489,11 +5494,8 @@ static void ast_rtp_prop_set(struct ast_rtp_instance *instance, enum ast_rtp_pro ast_free(rtp->rtcp); rtp->rtcp = NULL; } - return; } } - - return; } /*! \pre instance is locked */ diff --git a/tests/test_sdp.c b/tests/test_sdp.c new file mode 100644 index 000000000..33a6a2892 --- /dev/null +++ b/tests/test_sdp.c @@ -0,0 +1,856 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2017, Digium Inc. + * + * Mark Michelson <mmmichelson@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. + */ + +/*** MODULEINFO + <depend>TEST_FRAMEWORK</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" +#include "asterisk/test.h" +#include "asterisk/module.h" +#include "asterisk/sdp.h" +#include "asterisk/stream.h" +#include "asterisk/format.h" +#include "asterisk/format_cache.h" +#include "asterisk/format_cap.h" + +static int validate_o_line(struct ast_test *test, const struct ast_sdp_o_line *o_line, + const char *sdpowner, const char *address_type, const char *address) +{ + if (!o_line) { + return -1; + } + + if (strcmp(o_line->username, sdpowner)) { + ast_test_status_update(test, "Expected o-line SDP owner %s but got %s\n", + sdpowner, o_line->username); + return -1; + } + + if (strcmp(o_line->address_type, address_type)) { + ast_test_status_update(test, "Expected o-line SDP address type %s but got %s\n", + address_type, o_line->address_type); + return -1; + } + + if (strcmp(o_line->address, address)) { + ast_test_status_update(test, "Expected o-line SDP address %s but got %s\n", + address, o_line->address); + return -1; + } + + ast_test_status_update(test, "SDP o-line is as expected!\n"); + return 0; +} + +static int validate_c_line(struct ast_test *test, const struct ast_sdp_c_line *c_line, + const char *address_type, const char *address) +{ + if (strcmp(c_line->address_type, address_type)) { + ast_test_status_update(test, "Expected c-line SDP address type %s but got %s\n", + address_type, c_line->address_type); + return -1; + } + + if (strcmp(c_line->address, address)) { + ast_test_status_update(test, "Expected c-line SDP address %s but got %s\n", + address, c_line->address); + return -1; + } + + ast_test_status_update(test, "SDP c-line is as expected!\n"); + return 0; +} + +static int validate_m_line(struct ast_test *test, const struct ast_sdp_m_line *m_line, + const char *media_type, int num_payloads) +{ + 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 (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)); + return -1; + } + + ast_test_status_update(test, "SDP m-line is as expected\n"); + return 0; +} + +static int validate_rtpmap(struct ast_test *test, const struct ast_sdp_m_line *m_line, + const char *media_name) +{ + struct ast_sdp_a_line *a_line; + int i; + + for (i = 0; i < ast_sdp_m_get_a_count(m_line); ++i) { + struct ast_sdp_rtpmap *rtpmap; + int match; + + a_line = ast_sdp_m_get_a(m_line, i); + if (strcmp(a_line->name, "rtpmap")) { + continue; + } + + rtpmap = ast_sdp_a_get_rtpmap(a_line); + if (!rtpmap) { + return -1; + } + + match = !strcmp(rtpmap->encoding_name, media_name); + + ast_sdp_rtpmap_free(rtpmap); + if (match) { + return 0; + } + } + + ast_test_status_update(test, "Could not find rtpmap with encoding name %s\n", media_name); + + return -1; +} + +static enum ast_test_result_state validate_t38(struct ast_test *test, const struct ast_sdp_m_line *m_line) +{ + struct ast_sdp_a_line *a_line; + + a_line = ast_sdp_m_find_attribute(m_line, "T38FaxVersion", -1); + ast_test_validate(test, a_line && !strcmp(a_line->value, "0")); + + a_line = ast_sdp_m_find_attribute(m_line, "T38FaxMaxBitRate", -1); + ast_test_validate(test, a_line && !strcmp(a_line->value, "14400")); + + a_line = ast_sdp_m_find_attribute(m_line, "T38FaxRateManagement", -1); + ast_test_validate(test, a_line && !strcmp(a_line->value, "transferredTCF")); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(invalid_rtpmap) +{ + /* a=rtpmap: is already assumed. This is the part after that */ + static const char *invalids[] = { + "J PCMU/8000", + "0 PCMU:8000", + "0 PCMU/EIGHT-THOUSAND", + "0 PCMU/8000million/2", + "0 PCMU//2", + "0 /8000/2", + "0 PCMU/8000/", + "0 PCMU/8000million", + }; + int i; + enum ast_test_result_state res = AST_TEST_PASS; + + switch(cmd) { + case TEST_INIT: + info->name = "invalid_rtpmap"; + info->category = "/main/sdp/"; + info->summary = "Ensure invalid rtpmaps are rejected"; + info->description = + "Try to convert several invalid rtpmap attributes. If\n" + "any succeeds, the test fails."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + for (i = 0; i < ARRAY_LEN(invalids); ++i) { + struct ast_sdp_a_line *a_line; + struct ast_sdp_rtpmap *rtpmap; + + a_line = ast_sdp_a_alloc("rtpmap", invalids[i]); + rtpmap = ast_sdp_a_get_rtpmap(a_line); + if (rtpmap) { + ast_test_status_update(test, "Invalid rtpmap '%s' was accepted as valid\n", + invalids[i]); + res = AST_TEST_FAIL; + } + ast_sdp_a_free(a_line); + ast_sdp_rtpmap_free(rtpmap); + } + + return res; +} + +AST_TEST_DEFINE(rtpmap) +{ + static const char *valids[] = { + "0 PCMU/8000", + "107 opus/48000/2", + }; + static int payloads[] = { + 0, + 107, + }; + static const char *encoding_names[] = { + "PCMU", + "opus", + }; + static int clock_rates[] = { + 8000, + 48000, + }; + static const char *encoding_parameters[] = { + "", + "2", + }; + int i; + enum ast_test_result_state res = AST_TEST_PASS; + + switch(cmd) { + case TEST_INIT: + info->name = "rtpmap"; + info->category = "/main/sdp/"; + info->summary = "Ensure rtpmap attribute values are parsed correctly"; + info->description = + "Parse several valid rtpmap attributes. Ensure that the parsed values\n" + "are what we expect"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + for (i = 0; i < ARRAY_LEN(valids); ++i) { + struct ast_sdp_a_line *a_line; + struct ast_sdp_rtpmap *rtpmap; + + a_line = ast_sdp_a_alloc("rtpmap", valids[i]); + rtpmap = ast_sdp_a_get_rtpmap(a_line); + if (!rtpmap) { + ast_test_status_update(test, "Valid rtpmap '%s' was rejected as invalid\n", + valids[i]); + res = AST_TEST_FAIL; + continue; + } + if (rtpmap->payload != payloads[i]) { + ast_test_status_update(test, "RTPmap payload '%d' does not match expected '%d'\n", + rtpmap->payload, payloads[i]); + res = AST_TEST_FAIL; + } + if (strcmp(rtpmap->encoding_name, encoding_names[i])) { + ast_test_status_update(test, "RTPmap encoding_name '%s' does not match expected '%s'\n", + rtpmap->encoding_name, encoding_names[i]); + res = AST_TEST_FAIL; + } + if (rtpmap->clock_rate != clock_rates[i]) { + ast_test_status_update(test, "RTPmap clock rate '%d' does not match expected '%d'\n", + rtpmap->clock_rate, clock_rates[i]); + res = AST_TEST_FAIL; + } + if (strcmp(rtpmap->encoding_parameters, encoding_parameters[i])) { + ast_test_status_update(test, "RTPmap encoding_parameter '%s' does not match expected '%s'\n", + rtpmap->encoding_parameters, encoding_parameters[i]); + res = AST_TEST_FAIL; + } + ast_sdp_a_free(a_line); + ast_sdp_rtpmap_free(rtpmap); + } + + return res; +} + +AST_TEST_DEFINE(find_attr) +{ + enum ast_test_result_state res = AST_TEST_PASS; + struct ast_sdp_m_line *m_line; + struct ast_sdp_a_line *a_line; + + switch(cmd) { + case TEST_INIT: + info->name = "find_attr"; + info->category = "/main/sdp/"; + info->summary = "Ensure that finding attributes works as expected"; + info->description = + "An SDP m-line is created, and two attributes are added.\n" + "We then attempt a series of attribute-finding calls that are expected to work\n" + "followed by a series of attribute-finding calls that are expected fo fail."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + m_line = ast_sdp_m_alloc("audio", 666, 1, "RTP/AVP", NULL); + if (!m_line) { + res = AST_TEST_FAIL; + goto end; + } + a_line = ast_sdp_a_alloc("foo", "0 bar"); + if (!a_line) { + res = AST_TEST_FAIL; + goto end; + } + ast_sdp_m_add_a(m_line, a_line); + + a_line = ast_sdp_a_alloc("baz", "howdy"); + if (!a_line) { + res = AST_TEST_FAIL; + goto end; + } + ast_sdp_m_add_a(m_line, a_line); + + /* These should work */ + a_line = ast_sdp_m_find_attribute(m_line, "foo", 0); + if (!a_line) { + ast_test_status_update(test, "Failed to find attribute 'foo' with payload '0'\n"); + res = AST_TEST_FAIL; + } + a_line = ast_sdp_m_find_attribute(m_line, "foo", -1); + if (!a_line) { + ast_test_status_update(test, "Failed to find attribute 'foo' with unspecified payload\n"); + res = AST_TEST_FAIL; + } + a_line = ast_sdp_m_find_attribute(m_line, "baz", -1); + if (!a_line) { + ast_test_status_update(test, "Failed to find attribute 'baz' with unspecified payload\n"); + res = AST_TEST_FAIL; + } + + /* These should fail */ + a_line = ast_sdp_m_find_attribute(m_line, "foo", 1); + if (a_line) { + ast_test_status_update(test, "Found non-existent attribute 'foo' with payload '1'\n"); + res = AST_TEST_FAIL; + } + a_line = ast_sdp_m_find_attribute(m_line, "baz", 0); + if (a_line) { + ast_test_status_update(test, "Found non-existent attribute 'baz' with payload '0'\n"); + res = AST_TEST_FAIL; + } + a_line = ast_sdp_m_find_attribute(m_line, "wibble", 0); + if (a_line) { + ast_test_status_update(test, "Found non-existent attribute 'wibble' with payload '0'\n"); + res = AST_TEST_FAIL; + } + a_line = ast_sdp_m_find_attribute(m_line, "wibble", -1); + if (a_line) { + ast_test_status_update(test, "Found non-existent attribute 'foo' with unspecified payload\n"); + res = AST_TEST_FAIL; + } + +end: + ast_sdp_m_free(m_line); + return res; +} + +struct sdp_format { + enum ast_media_type type; + const char *formats; +}; + +static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_format *formats) +{ + struct ast_stream_topology *topology = NULL; + struct ast_sdp_state *state = NULL; + struct ast_sdp_options *options; + int i; + + options = ast_sdp_options_alloc(); + if (!options) { + goto end; + } + ast_sdp_options_set_media_address(options, "127.0.0.1"); + ast_sdp_options_set_sdpowner(options, "me"); + ast_sdp_options_set_rtp_engine(options, "asterisk"); + ast_sdp_options_set_impl(options, AST_SDP_IMPL_PJMEDIA); + + topology = ast_stream_topology_alloc(); + if (!topology) { + goto end; + } + + 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) { + goto end; + } + if (ast_format_cap_update_by_allow_disallow(caps, formats[i].formats, 1) < 0) { + goto end; + } + stream = ast_stream_alloc("sure_thing", formats[i].type); + if (!stream) { + goto end; + } + ast_stream_set_formats(stream, caps); + ast_stream_topology_append_stream(topology, stream); + } + + state = ast_sdp_state_alloc(topology, options); + if (!state) { + goto end; + } + +end: + ast_stream_topology_free(topology); + if (!state) { + ast_sdp_options_free(options); + } + + return state; +} + +AST_TEST_DEFINE(topology_to_sdp) +{ + enum ast_test_result_state res = AST_TEST_FAIL; + struct ast_sdp_state *sdp_state = NULL; + const struct ast_sdp *sdp = NULL; + struct ast_sdp_m_line *m_line = NULL; + struct sdp_format 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 = "topology_to_sdp"; + info->category = "/main/sdp/"; + info->summary = "Convert a topology into an SDP"; + info->description = + "Ensure SDPs get converted to expected stream topology"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + sdp_state = build_sdp_state(ARRAY_LEN(formats), formats); + if (!sdp_state) { + goto end; + } + + sdp = ast_sdp_state_get_local_sdp(sdp_state); + if (!sdp) { + goto end; + } + + if (validate_o_line(test, sdp->o_line, "me", "IP4", "127.0.0.1")) { + goto end; + } + + if (validate_c_line(test, sdp->c_line, "IP4", "127.0.0.1")) { + goto end; + } + + if (ast_sdp_get_m_count(sdp) != 3) { + ast_test_status_update(test, "Unexpected number of streams in generated SDP: %d\n", + ast_sdp_get_m_count(sdp)); + goto end; + } + + m_line = ast_sdp_get_m(sdp, 0); + + if (validate_m_line(test, m_line, "audio", 4)) { + goto end; + } + + if (validate_rtpmap(test, m_line, "PCMU")) { + goto end; + } + + if (validate_rtpmap(test, m_line, "PCMA")) { + goto end; + } + + if (validate_rtpmap(test, m_line, "G722")) { + goto end; + } + + if (validate_rtpmap(test, m_line, "opus")) { + goto end; + } + + m_line = ast_sdp_get_m(sdp, 1); + if (validate_m_line(test, m_line, "video", 2)) { + goto end; + } + + if (validate_rtpmap(test, m_line, "VP8")) { + goto end; + } + + if (validate_rtpmap(test, m_line, "H264")) { + goto end; + } + + m_line = ast_sdp_get_m(sdp, 2); + if (validate_m_line(test, m_line, "image", 1)) { + goto end; + } + if (validate_t38(test, m_line) != AST_TEST_PASS) { + goto end; + } + + res = AST_TEST_PASS; + +end: + ast_sdp_state_free(sdp_state); + return res; +} + +static int validate_formats(struct ast_test *test, struct ast_stream_topology *topology, int index, + enum ast_media_type type, int format_count, const char **expected_formats) +{ + struct ast_stream *stream; + struct ast_format_cap *caps; + struct ast_format *format; + int i; + + stream = ast_stream_topology_get_stream(topology, index); + if (ast_stream_get_type(stream) != type) { + ast_test_status_update(test, "Unexpected stream type encountered\n"); + return -1; + } + caps = ast_stream_get_formats(stream); + + if (ast_format_cap_count(caps) != format_count) { + ast_test_status_update(test, "Unexpected format count '%d'. Expecting '%d'\n", + (int) ast_format_cap_count(caps), format_count); + return -1; + } + + for (i = 0; i < ast_format_cap_count(caps); ++i) { + format = ast_format_cap_get_format(caps, i); + if (strcmp(ast_format_get_name(format), expected_formats[i])) { + ast_test_status_update(test, "Unexpected format '%s'at index %d. Expected '%s'\n", + ast_format_get_name(format), i, expected_formats[i]); + return -1; + } + } + + return 0; +} + +AST_TEST_DEFINE(sdp_to_topology) +{ + enum ast_test_result_state res = AST_TEST_PASS; + struct ast_sdp_state *sdp_state; + const struct ast_sdp *sdp; + struct ast_stream_topology *topology = NULL; + struct sdp_format sdp_formats[] = { + { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" }, + { AST_MEDIA_TYPE_VIDEO, "h264,vp8" }, + { AST_MEDIA_TYPE_IMAGE, "t38" }, + }; + static const char *expected_audio_formats[] = { + "ulaw", + "alaw", + "g722", + "opus", + }; + static const char *expected_video_formats[] = { + "h264", + "vp8", + }; + static const char *expected_image_formats[] = { + "t38", + }; + + switch(cmd) { + case TEST_INIT: + info->name = "sdp_to_topology"; + info->category = "/main/sdp/"; + info->summary = "Convert an SDP into a topology"; + info->description = + "Ensure SDPs get converted to expected stream topology"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + sdp_state = build_sdp_state(ARRAY_LEN(sdp_formats), sdp_formats); + if (!sdp_state) { + res = AST_TEST_FAIL; + goto end; + } + + sdp = ast_sdp_state_get_local_sdp(sdp_state); + if (!sdp) { + res = AST_TEST_FAIL; + goto end; + } + + topology = ast_get_topology_from_sdp(sdp); + + if (ast_stream_topology_get_count(topology) != 3) { + ast_test_status_update(test, "Unexpected topology count '%d'. Expecting 2\n", + ast_stream_topology_get_count(topology)); + res = AST_TEST_FAIL; + goto end; + } + + if (validate_formats(test, topology, 0, AST_MEDIA_TYPE_AUDIO, + ARRAY_LEN(expected_audio_formats), expected_audio_formats)) { + res = AST_TEST_FAIL; + goto end; + } + + if (validate_formats(test, topology, 1, AST_MEDIA_TYPE_VIDEO, + ARRAY_LEN(expected_video_formats), expected_video_formats)) { + res = AST_TEST_FAIL; + goto end; + } + + if (validate_formats(test, topology, 2, AST_MEDIA_TYPE_IMAGE, + ARRAY_LEN(expected_image_formats), expected_image_formats)) { + res = AST_TEST_FAIL; + goto end; + } + +end: + ast_sdp_state_free(sdp_state); + ast_stream_topology_free(topology); + return res; +} + +static int validate_merged_sdp(struct ast_test *test, const struct ast_sdp *sdp) +{ + struct ast_sdp_m_line *m_line; + + if (!sdp) { + return -1; + } + + 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; + } + + /* The other audio formats should *NOT* be present */ + if (!validate_rtpmap(test, m_line, "PCMA")) { + return -1; + } + + if (!validate_rtpmap(test, m_line, "G722")) { + return -1; + } + + if (!validate_rtpmap(test, m_line, "opus")) { + return -1; + } + + m_line = ast_sdp_get_m(sdp, 1); + + if (validate_m_line(test, m_line, "video", 1)) { + return -1; + } + + if (validate_rtpmap(test, m_line, "VP8")) { + return -1; + } + + if (!validate_rtpmap(test, m_line, "H264")) { + return -1; + } + + return 0; +} + +AST_TEST_DEFINE(sdp_merge_symmetric) +{ + 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" }, + }; + static const struct sdp_format answerer_formats[] = { + { AST_MEDIA_TYPE_AUDIO, "ulaw" }, + { AST_MEDIA_TYPE_VIDEO, "vp8" }, + }; + + 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); + if (!sdp_state_offerer) { + res = AST_TEST_FAIL; + goto end; + } + + sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats); + if (!sdp_state_answerer) { + res = AST_TEST_FAIL; + goto end; + } + + offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer); + if (!offerer_sdp) { + res = AST_TEST_FAIL; + 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) { + res = AST_TEST_FAIL; + goto end; + } + + 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; + } + +end: + ast_sdp_state_free(sdp_state_offerer); + ast_sdp_state_free(sdp_state_answerer); + + return res; +} + +AST_TEST_DEFINE(sdp_merge_crisscross) +{ + 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" }, + }; + static const struct sdp_format answerer_formats[] = { + { AST_MEDIA_TYPE_VIDEO, "vp8" }, + { AST_MEDIA_TYPE_AUDIO, "ulaw" }, + }; + + switch(cmd) { + case TEST_INIT: + info->name = "sdp_merge_crisscross"; + 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. 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"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats); + if (!sdp_state_offerer) { + res = AST_TEST_FAIL; + goto end; + } + + sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats); + if (!sdp_state_answerer) { + res = AST_TEST_FAIL; + goto end; + } + + offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer); + if (!offerer_sdp) { + res = AST_TEST_FAIL; + 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) { + res = AST_TEST_FAIL; + goto end; + } + + 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; + } + +end: + ast_sdp_state_free(sdp_state_offerer); + ast_sdp_state_free(sdp_state_answerer); + + return res; +} + +static int unload_module(void) +{ + AST_TEST_UNREGISTER(invalid_rtpmap); + AST_TEST_UNREGISTER(rtpmap); + 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); + + return 0; +} + +static int load_module(void) +{ + AST_TEST_REGISTER(invalid_rtpmap); + AST_TEST_REGISTER(rtpmap); + 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); + + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "SDP tests"); diff --git a/third-party/pjproject/Makefile.rules b/third-party/pjproject/Makefile.rules index e8eb46643..c0be1cbdf 100644 --- a/third-party/pjproject/Makefile.rules +++ b/third-party/pjproject/Makefile.rules @@ -24,6 +24,7 @@ PJPROJECT_CONFIG_OPTS = --prefix=/opt/pjproject \ --disable-ffmpeg \ --disable-openh264 \ --disable-ipp \ + --disable-libwebrtc \ --without-external-pa \ --without-external-srtp \ --without-external-webrtc |