diff options
Diffstat (limited to 'pjmedia/src')
-rw-r--r-- | pjmedia/src/pjmedia/errno.c | 139 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/jbuf.c | 2 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/sdp.c | 998 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/sdp_cmp.c | 292 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/sdp_neg.c | 815 | ||||
-rw-r--r-- | pjmedia/src/test/main.c | 24 | ||||
-rw-r--r-- | pjmedia/src/test/sdp_neg_test.c | 1209 | ||||
-rw-r--r-- | pjmedia/src/test/test.c | 75 | ||||
-rw-r--r-- | pjmedia/src/test/test.h | 37 |
9 files changed, 3181 insertions, 410 deletions
diff --git a/pjmedia/src/pjmedia/errno.c b/pjmedia/src/pjmedia/errno.c new file mode 100644 index 00000000..ccf5c8d8 --- /dev/null +++ b/pjmedia/src/pjmedia/errno.c @@ -0,0 +1,139 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia/errno.h> +#include <pj/string.h> + + + +/* PJMEDIA's own error codes/messages + * MUST KEEP THIS ARRAY SORTED!! + * Message must be limited to 64 chars! + */ +static const struct +{ + int code; + const char *msg; +} err_str[] = +{ + /* Generic PJMEDIA errors, shouldn't be used! */ + { PJMEDIA_ERROR, "Unspecified PJMEDIA error" }, + + /* SDP error. */ + { PJMEDIA_SDP_EINSDP, "Invalid SDP descriptor" }, + { PJMEDIA_SDP_EINVER, "Invalid SDP version line" }, + { PJMEDIA_SDP_EINORIGIN, "Invalid SDP origin line" }, + { PJMEDIA_SDP_EINTIME, "Invalid SDP time line"}, + { PJMEDIA_SDP_EINNAME, "SDP name/subject line is empty"}, + { PJMEDIA_SDP_EINCONN, "Invalid SDP connection line"}, + { PJMEDIA_SDP_EMISSINGCONN, "Missing SDP connection info line"}, + { PJMEDIA_SDP_EINATTR, "Invalid SDP attributes"}, + { PJMEDIA_SDP_EINRTPMAP, "Invalid SDP rtpmap attribute"}, + { PJMEDIA_SDP_ERTPMAPTOOLONG, "SDP rtpmap attribute too long"}, + { PJMEDIA_SDP_EMISSINGRTPMAP, "Missing SDP rtpmap for dynamic payload type"}, + { PJMEDIA_SDP_EINMEDIA, "Invalid SDP media line" }, + { PJMEDIA_SDP_ENOFMT, "No SDP payload format in the media line" }, + { PJMEDIA_SDP_EINPT, "Invalid SDP payload type in media line" }, + { PJMEDIA_SDP_EINFMTP, "Invalid SDP fmtp attribute" }, + + /* SDP negotiator errors. */ + { PJMEDIA_SDPNEG_EINSTATE, "Invalid SDP negotiator state for operation" }, + { PJMEDIA_SDPNEG_ENOACTIVE, "No active SDP in SDP negotiator" }, + { PJMEDIA_SDPNEG_EMISMEDIA, "SDP media count mismatch in offer/answer" }, + { PJMEDIA_SDPNEG_EINVANSMEDIA, "SDP media type mismatch in offer/answer" }, + { PJMEDIA_SDPNEG_EINVANSTP, "SDP media transport type mismatch in offer/answer" }, + { PJMEDIA_SDPNEG_EANSNOMEDIA, "No common SDP media payload in answer" }, + + /* SDP comparison results */ + { PJMEDIA_SDP_EMEDIANOTEQUAL, "SDP media descriptor not equal" }, + { PJMEDIA_SDP_EPORTNOTEQUAL, "Port in SDP media descriptor not equal" }, + { PJMEDIA_SDP_ETPORTNOTEQUAL, "Transport in SDP media descriptor not equal" }, + { PJMEDIA_SDP_EFORMATNOTEQUAL, "Format in SDP media descriptor not equal" }, + { PJMEDIA_SDP_ECONNNOTEQUAL, "SDP connection line not equal" }, + { PJMEDIA_SDP_EATTRNOTEQUAL, "SDP attributes not equal" }, + { PJMEDIA_SDP_EDIRNOTEQUAL, "SDP media direction not equal" }, + { PJMEDIA_SDP_EFMTPNOTEQUAL, "SDP fmtp attribute not equal" }, + { PJMEDIA_SDP_ERTPMAPNOTEQUAL, "SDP rtpmap attribute not equal" }, + { PJMEDIA_SDP_ESESSNOTEQUAL, "SDP session descriptor not equal" }, + { PJMEDIA_SDP_EORIGINNOTEQUAL, "SDP origin line not equal" }, + { PJMEDIA_SDP_ENAMENOTEQUAL, "SDP name/subject line not equal" }, + { PJMEDIA_SDP_ETIMENOTEQUAL, "SDP time line not equal" }, +}; + + + +/* + * pjmedia_strerror() + */ +PJ_DEF(pj_str_t) pjmedia_strerror( pj_status_t statcode, + char *buf, pj_size_t bufsize ) +{ + pj_str_t errstr; + + if (statcode >= PJMEDIA_ERRNO_START && + statcode < PJMEDIA_ERRNO_START + PJ_ERRNO_SPACE_SIZE) + { + /* Find the error in the table. + * Use binary search! + */ + int first = 0; + int n = PJ_ARRAY_SIZE(err_str); + + while (n > 0) { + int half = n/2; + int mid = first + half; + + if (err_str[mid].code < statcode) { + first = mid+1; + n -= (half+1); + } else if (err_str[mid].code > statcode) { + n = half; + } else { + first = mid; + break; + } + } + + + if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) { + pj_str_t msg; + + msg.ptr = (char*)err_str[first].msg; + msg.slen = pj_native_strlen(err_str[first].msg); + + errstr.ptr = buf; + pj_strncpy_with_null(&errstr, &msg, bufsize); + return errstr; + + } else { + /* Error not found. */ + errstr.ptr = buf; + errstr.slen = pj_snprintf(buf, bufsize, + "Unknown error %d", + statcode); + + return errstr; + } + } + else { + /* Not our code. Give it to PJLIB. */ + return pj_strerror(statcode, buf, bufsize); + } + +} + diff --git a/pjmedia/src/pjmedia/jbuf.c b/pjmedia/src/pjmedia/jbuf.c index ce00d970..7661bcce 100644 --- a/pjmedia/src/pjmedia/jbuf.c +++ b/pjmedia/src/pjmedia/jbuf.c @@ -300,7 +300,7 @@ PJ_DEF(pj_status_t) pj_jb_put( JB *jb, pj_uint32_t extseq, void *buf ) jb->level++; } - if (jb->lst.count > jb->max_level) + if ((int)jb->lst.count > jb->max_level) jb->max_level++; jb->last_op = JB_PUT; diff --git a/pjmedia/src/pjmedia/sdp.c b/pjmedia/src/pjmedia/sdp.c index afd746ee..2f6dcfe3 100644 --- a/pjmedia/src/pjmedia/sdp.c +++ b/pjmedia/src/pjmedia/sdp.c @@ -17,13 +17,17 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <pjmedia/sdp.h> +#include <pjmedia/errno.h> #include <pjlib-util/scanner.h> +#include <pj/array.h> #include <pj/except.h> #include <pj/log.h> #include <pj/os.h> #include <pj/string.h> #include <pj/pool.h> #include <pj/assert.h> +#include <pj/ctype.h> + enum { SKIP_WS = 0, @@ -31,95 +35,32 @@ enum { }; #define TOKEN "-.!%*_=`'~" #define NTP_OFFSET ((pj_uint32_t)2208988800) -#define LOG_THIS "sdp" +#define THIS_FILE "sdp.c" -/* - * Prototypes for line parser. - */ -static void parse_version(pj_scanner *scanner); -static void parse_origin(pj_scanner *scanner, pjsdp_session_desc *ses); -static void parse_time(pj_scanner *scanner, pjsdp_session_desc *ses); -static void parse_generic_line(pj_scanner *scanner, pj_str_t *str); -static void parse_connection_info(pj_scanner *scanner, pjsdp_conn_info *conn); -static pjsdp_attr *parse_attr(pj_pool_t *pool, pj_scanner *scanner); -static void parse_media(pj_scanner *scanner, pjsdp_media_desc *med); - -/* - * Prototypes for attribute parsers. - */ -static pjsdp_rtpmap_attr * parse_rtpmap_attr( pj_pool_t *pool, pj_scanner *scanner ); -static pjsdp_attr_string * parse_generic_string_attr( pj_pool_t *pool, pj_scanner *scanner ); -static pjsdp_attr_num * parse_generic_num_attr( pj_pool_t *pool, pj_scanner *scanner ); -static pjsdp_attr * parse_name_only_attr( pj_pool_t *pool, pj_scanner *scanner ); -static pjsdp_fmtp_attr * parse_fmtp_attr( pj_pool_t *pool, pj_scanner *scanner ); - - -/* - * Prototypes for functions to print attribute. - * All of them returns integer for the length printed, or -1 on error. - */ -static int print_rtpmap_attr(const pjsdp_rtpmap_attr *attr, - char *buf, int length); -static int print_generic_string_attr(const pjsdp_attr_string *attr, - char *buf, int length); -static int print_generic_num_attr(const pjsdp_attr_num *attr, - char *buf, int length); -static int print_name_only_attr(const pjsdp_attr *attr, - char *buf, int length); -static int print_fmtp_attr(const pjsdp_fmtp_attr *attr, - char *buf, int length); - -/* - * Prototypes for cloning attributes. - */ -static pjsdp_attr* clone_rtpmap_attr (pj_pool_t *pool, const pjsdp_attr *rhs); -static pjsdp_attr* clone_generic_string_attr (pj_pool_t *pool, const pjsdp_attr *rhs); -static pjsdp_attr* clone_generic_num_attr (pj_pool_t *pool, const pjsdp_attr *rhs); -static pjsdp_attr* clone_name_only_attr (pj_pool_t *pool, const pjsdp_attr *rhs); -static pjsdp_attr* clone_fmtp_attr (pj_pool_t *pool, const pjsdp_attr *rhs); +typedef struct parse_context +{ + pj_status_t last_error; +} parse_context; /* - * Prototypes + * Prototypes for line parser. */ -static void init_sdp_parser(void); +static void parse_version(pj_scanner *scanner, parse_context *ctx); +static void parse_origin(pj_scanner *scanner, pjmedia_sdp_session *ses, + parse_context *ctx); +static void parse_time(pj_scanner *scanner, pjmedia_sdp_session *ses, + parse_context *ctx); +static void parse_generic_line(pj_scanner *scanner, pj_str_t *str, + parse_context *ctx); +static void parse_connection_info(pj_scanner *scanner, pjmedia_sdp_conn *conn, + parse_context *ctx); +static pjmedia_sdp_attr *parse_attr(pj_pool_t *pool, pj_scanner *scanner, + parse_context *ctx); +static void parse_media(pj_scanner *scanner, pjmedia_sdp_media *med, + parse_context *ctx); -typedef void * (*FPARSE)(pj_pool_t *pool, pj_scanner *scanner); -typedef int (*FPRINT)(const void *attr, char *buf, int length); -typedef pjsdp_attr* (*FCLONE)(pj_pool_t *pool, const pjsdp_attr *rhs); - -/* - * Array of functions to print attribute. - */ -static struct attr_map_rec -{ - pj_str_t name; - FPARSE parse_attr; - FPRINT print_attr; - FCLONE clone; -} attr_map[] = -{ - {{"rtpmap", 6}, (FPARSE)&parse_rtpmap_attr, (FPRINT)&print_rtpmap_attr, (FCLONE)&clone_rtpmap_attr}, - {{"cat", 3}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"keywds", 6}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"tool", 4}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"ptime", 5}, (FPARSE)&parse_generic_num_attr, (FPRINT)&print_generic_num_attr, (FCLONE)&clone_generic_num_attr}, - {{"recvonly", 8}, (FPARSE)&parse_name_only_attr, (FPRINT)&print_name_only_attr, (FCLONE)&clone_name_only_attr}, - {{"sendonly", 8}, (FPARSE)&parse_name_only_attr, (FPRINT)&print_name_only_attr, (FCLONE)&clone_name_only_attr}, - {{"sendrecv", 8}, (FPARSE)&parse_name_only_attr, (FPRINT)&print_name_only_attr, (FCLONE)&clone_name_only_attr}, - {{"orient", 6}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"type", 4}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"charset", 7}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"sdplang", 7}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"lang", 4}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"framerate", 9}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"quality", 7}, (FPARSE)&parse_generic_num_attr, (FPRINT)&print_generic_num_attr, (FCLONE)&clone_generic_num_attr}, - {{"fmtp", 4}, (FPARSE)&parse_fmtp_attr, (FPRINT)&print_fmtp_attr, (FCLONE)&clone_fmtp_attr}, - {{"inactive", 8}, (FPARSE)&parse_name_only_attr, (FPRINT)&print_name_only_attr, (FCLONE)&clone_name_only_attr}, - {{"", 0}, NULL, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr} -}; - /* * Scanner character specification. */ @@ -143,213 +84,358 @@ static void init_sdp_parser(void) pj_cis_add_str(&cs_token, TOKEN); } -static int print_rtpmap_attr(const pjsdp_rtpmap_attr *rtpmap, - char *buf, int len) +PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_create( pj_pool_t *pool, + const char *name, + const pj_str_t *value) { - char *p = buf; - - if (len < 16+rtpmap->encoding_name.slen+rtpmap->parameter.slen) { - return -1; - } - - /* colon and payload type. */ - *p++ = ':'; - len = pj_utoa(rtpmap->payload_type, p); - p += len; + pjmedia_sdp_attr *attr; - /* space, encoding name */ - *p++ = ' '; - pj_memcpy(p, rtpmap->encoding_name.ptr, rtpmap->encoding_name.slen); - p += rtpmap->encoding_name.slen; + PJ_ASSERT_RETURN(pool && name, NULL); - /* slash, clock-rate. */ - *p++ = '/'; - len = pj_utoa(rtpmap->clock_rate, p); - p += len; + attr = pj_pool_alloc(pool, sizeof(pjmedia_sdp_attr)); + pj_strdup2(pool, &attr->name, name); - /* optionally add encoding parameter. */ - if (rtpmap->parameter.slen) { - *p++ = '/'; - pj_memcpy(p, rtpmap->parameter.ptr, rtpmap->parameter.slen); - p += rtpmap->parameter.slen; + if (value) + pj_strdup(pool, &attr->value, value); + else { + attr->value.ptr = NULL; + attr->value.slen = 0; } - return p-buf; + return attr; } -static int print_generic_string_attr(const pjsdp_attr_string *attr, - char *buf, int len) +PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_clone(pj_pool_t *pool, + const pjmedia_sdp_attr *rhs) { - char *p = buf; + pjmedia_sdp_attr *attr; + + PJ_ASSERT_RETURN(pool && rhs, NULL); - if (len < attr->value.slen + 4) { - return -1; - } + attr = pj_pool_alloc(pool, sizeof(pjmedia_sdp_attr)); - /* colon and attribute value. */ - *p++ = ':'; - pj_memcpy(p, attr->value.ptr, attr->value.slen); - p += attr->value.slen; + pj_strdup(pool, &attr->name, &rhs->name); + pj_strdup(pool, &attr->value, &rhs->value); - return p-buf; + return attr; } -static int print_generic_num_attr(const pjsdp_attr_num *attr, char *buf, int len) +PJ_DEF(pjmedia_sdp_attr*) +pjmedia_sdp_attr_find (unsigned count, + const pjmedia_sdp_attr *const attr_array[], + const pj_str_t *name, + const pj_str_t *c_fmt) { - char *p = buf; + char fmtbuf[16]; + pj_str_t fmt = { NULL, 0}; + unsigned i; - if (len < 10) { - return -1; + if (c_fmt) { + /* To search the format, we prepend the string with a colon and + * append space + */ + PJ_ASSERT_RETURN(c_fmt->slen<sizeof(fmtbuf)-2, NULL); + fmt.ptr = fmtbuf; + fmt.slen = c_fmt->slen + 2; + fmtbuf[0] = ':'; + pj_memcpy(fmt.ptr+1, c_fmt->ptr, c_fmt->slen); + fmtbuf[c_fmt->slen+1] = ' '; + + } + + for (i=0; i<count; ++i) { + if (pj_strcmp(&attr_array[i]->name, name) == 0) { + const pjmedia_sdp_attr *a = attr_array[i]; + if (c_fmt) { + if (a->value.slen > fmt.slen && + pj_strncmp(&a->value, &fmt, fmt.slen)==0) + { + return (pjmedia_sdp_attr*)a; + } + } else + return (pjmedia_sdp_attr*)a; + } } - *p++ = ':'; - return pj_utoa(attr->value, p); + return NULL; } -static int print_name_only_attr(const pjsdp_attr *attr, char *buf, int len) +PJ_DEF(pjmedia_sdp_attr*) +pjmedia_sdp_attr_find2(unsigned count, + const pjmedia_sdp_attr *const attr_array[], + const char *c_name, + const pj_str_t *c_fmt) { - PJ_UNUSED_ARG(attr); - PJ_UNUSED_ARG(buf); - PJ_UNUSED_ARG(len); - return 0; + pj_str_t name; + + name.ptr = (char*)c_name; + name.slen = pj_native_strlen(c_name); + + return pjmedia_sdp_attr_find(count, attr_array, &name, c_fmt); } -static int print_fmtp_attr(const pjsdp_fmtp_attr *fmtp, char *buf, int len) + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_add(unsigned *count, + pjmedia_sdp_attr *attr_array[], + pjmedia_sdp_attr *attr) { - char *p = buf; + PJ_ASSERT_RETURN(count && attr_array && attr, PJ_EINVAL); + PJ_ASSERT_RETURN(*count < PJSDP_MAX_ATTR, PJ_ETOOMANY); - if (len < 4+fmtp->format.slen+fmtp->param.slen) { - return -1; - } + attr_array[*count] = attr; + (*count)++; - /* colon and format. */ - *p++ = ':'; - pj_memcpy(p, fmtp->format.ptr, fmtp->format.slen); - p += fmtp->format.slen; + return PJ_SUCCESS; +} - /* space and parameter. */ - *p++ = ' '; - pj_memcpy(p, fmtp->param.ptr, fmtp->param.slen); - p += fmtp->param.slen; - return p-buf; +PJ_DEF(unsigned) pjmedia_sdp_attr_remove_all(unsigned *count, + pjmedia_sdp_attr *attr_array[], + const char *name) +{ + unsigned i, removed = 0; + pj_str_t attr_name; + + PJ_ASSERT_RETURN(count && attr_array && name, PJ_EINVAL); + + attr_name.ptr = (char*)name; + attr_name.slen = pj_native_strlen(name); + + for (i=0; i<*count; ) { + if (pj_strcmp(&attr_array[i]->name, &attr_name)==0) { + pj_array_erase(attr_array, sizeof(pjmedia_sdp_attr*), + *count, i); + --(*count); + ++removed; + } else { + ++i; + } + } + + return removed; } -static int print_attr(const pjsdp_attr *attr, char *buf, int len) +PJ_DEF(pj_status_t) pjmedia_sdp_attr_remove( unsigned *count, + pjmedia_sdp_attr *attr_array[], + pjmedia_sdp_attr *attr ) { - char *p = buf; - struct attr_map_rec *desc = &attr_map[attr->type]; - - if (len < 16) { - return -1; + unsigned i, removed=0; + + PJ_ASSERT_RETURN(count && attr_array && attr, PJ_EINVAL); + + for (i=0; i<*count; ) { + if (attr_array[i] == attr) { + pj_array_erase(attr_array, sizeof(pjmedia_sdp_attr*), + *count, i); + --(*count); + ++removed; + } else { + ++i; + } } - *p++ = 'a'; - *p++ = '='; - pj_memcpy(p, desc->name.ptr, desc->name.slen); - p += desc->name.slen; - - len = (*desc->print_attr)(attr, p, (buf+len)-p); - if (len < 0) { - return -1; - } - p += len; - *p++ = '\r'; - *p++ = '\n'; - return p-buf; + return removed ? PJ_SUCCESS : PJ_ENOTFOUND; } -static pjsdp_attr* clone_rtpmap_attr (pj_pool_t *pool, const pjsdp_attr *p) + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_rtpmap( const pjmedia_sdp_attr *attr, + pjmedia_sdp_rtpmap *rtpmap) { - const pjsdp_rtpmap_attr *rhs = (const pjsdp_rtpmap_attr*)p; - pjsdp_rtpmap_attr *attr = pj_pool_alloc (pool, sizeof(pjsdp_rtpmap_attr)); - if (!attr) - return NULL; + const char *p = attr->value.ptr; + const char *end = attr->value.ptr + attr->value.slen; + pj_str_t token; + + PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "rtpmap")==0, PJ_EINVALIDOP); + + /* rtpmap sample: + * a=rtpmap:98 L16/16000/2. + */ + + /* Eat the first ':' */ + if (*p != ':') return PJMEDIA_SDP_EINRTPMAP; + + /* Get ':' */ + ++p; + + /* Get payload type. */ + token.ptr = (char*)p; + while (pj_isdigit(*p) && p!=end) + ++p; + token.slen = p - token.ptr; + if (token.slen == 0) + return PJMEDIA_SDP_EINRTPMAP; + + rtpmap->pt = token; + + /* Expecting space after payload type. */ + if (*p != ' ') return PJMEDIA_SDP_EINRTPMAP; + + /* Get space. */ + ++p; + + /* Get encoding name. */ + token.ptr = (char*)p; + while (*p != '/' && p != end) + ++p; + token.slen = p - token.ptr; + if (token.slen == 0) + return PJMEDIA_SDP_EINRTPMAP; + rtpmap->enc_name = token; + + /* Expecting '/' after encoding name. */ + if (*p != '/') return PJMEDIA_SDP_EINRTPMAP; + + /* Get '/' */ + ++p; + + /* Get the clock rate. */ + token.ptr = (char*)p; + while (pj_isdigit(*p) && p != end) + ++p; + token.slen = p - token.ptr; + if (token.slen == 0) + return PJMEDIA_SDP_EINRTPMAP; + + rtpmap->clock_rate = pj_strtoul(&token); + + /* Expecting either '/' or EOF */ + if (*p != '/' && p != end) + return PJMEDIA_SDP_EINRTPMAP; + + if (*p == '/') { + ++p; + token.ptr = (char*)p; + token.slen = end-p; + rtpmap->param = token; + } else { + rtpmap->param.ptr = NULL; + rtpmap->param.slen = 0; + } - attr->type = rhs->type; - attr->payload_type = rhs->payload_type; - if (!pj_strdup (pool, &attr->encoding_name, &rhs->encoding_name)) return NULL; - attr->clock_rate = rhs->clock_rate; - if (!pj_strdup (pool, &attr->parameter, &rhs->parameter)) return NULL; + return PJ_SUCCESS; - return (pjsdp_attr*)attr; } -static pjsdp_attr* clone_generic_string_attr (pj_pool_t *pool, const pjsdp_attr *p) +PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_fmtp( const pjmedia_sdp_attr *attr, + pjmedia_sdp_fmtp *fmtp) { - const pjsdp_attr_string* rhs = (const pjsdp_attr_string*) p; - pjsdp_attr_string *attr = pj_pool_alloc (pool, sizeof(pjsdp_attr_string)); - if (!attr) - return NULL; + const char *p = attr->value.ptr; + const char *end = attr->value.ptr + attr->value.slen; + pj_str_t token; - attr->type = rhs->type; - if (!pj_strdup (pool, &attr->value, &rhs->value)) return NULL; + PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "fmtp")==0, PJ_EINVALIDOP); - return (pjsdp_attr*)attr; -} + /* fmtp BNF: + * a=fmtp:<format> <format specific parameter> + */ -static pjsdp_attr* clone_generic_num_attr (pj_pool_t *pool, const pjsdp_attr *p) -{ - const pjsdp_attr_num* rhs = (const pjsdp_attr_num*) p; - pjsdp_attr_num *attr = pj_pool_alloc (pool, sizeof(pjsdp_attr_num)); - if (!attr) - return NULL; + /* Eat the first ':' */ + if (*p != ':') return PJMEDIA_SDP_EINFMTP; - attr->type = rhs->type; - attr->value = rhs->value; + /* Get ':' */ + ++p; - return (pjsdp_attr*)attr; -} + /* Get format. */ + token.ptr = (char*)p; + while (pj_isdigit(*p) && p!=end) + ++p; + token.slen = p - token.ptr; + if (token.slen == 0) + return PJMEDIA_SDP_EINFMTP; -static pjsdp_attr* clone_name_only_attr (pj_pool_t *pool, const pjsdp_attr *rhs) -{ - pjsdp_attr *attr = pj_pool_alloc (pool, sizeof(pjsdp_attr)); - if (!attr) - return NULL; + fmtp->fmt = token; - attr->type = rhs->type; - return attr; -} + /* Expecting space after format. */ + if (*p != ' ') return PJMEDIA_SDP_EINFMTP; -static pjsdp_attr* clone_fmtp_attr (pj_pool_t *pool, const pjsdp_attr *p) -{ - const pjsdp_fmtp_attr* rhs = (const pjsdp_fmtp_attr*) p; - pjsdp_fmtp_attr *attr = pj_pool_alloc (pool, sizeof(pjsdp_fmtp_attr)); - if (!attr) - return NULL; + /* Get space. */ + ++p; - attr->type = rhs->type; - if (!pj_strdup (pool, &attr->format, &rhs->format)) return NULL; - if (!pj_strdup (pool, &attr->param, &rhs->param)) return NULL; + /* Set the remaining string as fmtp format parameter. */ + fmtp->fmt_param.ptr = (char*)p; + fmtp->fmt_param.slen = end - p; - return (pjsdp_attr*)attr; + return PJ_SUCCESS; } -PJ_DEF(pjsdp_attr*) pjsdp_attr_clone (pj_pool_t *pool, const pjsdp_attr *rhs) +PJ_DEF(pj_status_t) pjmedia_sdp_attr_to_rtpmap(pj_pool_t *pool, + const pjmedia_sdp_attr *attr, + pjmedia_sdp_rtpmap **p_rtpmap) { - struct attr_map_rec *desc; + PJ_ASSERT_RETURN(pool && attr && p_rtpmap, PJ_EINVAL); - if (rhs->type >= PJSDP_END_OF_ATTR) { - pj_assert(0); - return NULL; - } + *p_rtpmap = pj_pool_alloc(pool, sizeof(pjmedia_sdp_rtpmap)); + PJ_ASSERT_RETURN(*p_rtpmap, PJ_ENOMEM); - desc = &attr_map[rhs->type]; - return (*desc->clone) (pool, rhs); + return pjmedia_sdp_attr_get_rtpmap(attr, *p_rtpmap); } -PJ_DEF(const pjsdp_attr*) pjsdp_attr_find (int count, const pjsdp_attr *attr_array[], int type) + +PJ_DEF(pj_status_t) pjmedia_sdp_rtpmap_to_attr(pj_pool_t *pool, + const pjmedia_sdp_rtpmap *rtpmap, + pjmedia_sdp_attr **p_attr) { + pjmedia_sdp_attr *attr; + char tempbuf[64], *p, *endbuf; int i; - for (i=0; i<count; ++i) { - if (attr_array[i]->type == type) - return attr_array[i]; + /* Check arguments. */ + PJ_ASSERT_RETURN(pool && rtpmap && p_attr, PJ_EINVAL); + + /* Check that mandatory attributes are specified. */ + PJ_ASSERT_RETURN(rtpmap->enc_name.slen && rtpmap->clock_rate, + PJMEDIA_SDP_EINRTPMAP); + + /* Check size. */ + i = rtpmap->enc_name.slen + rtpmap->param.slen + 32; + if (i >= sizeof(tempbuf)-1) { + pj_assert(!"rtpmap attribute is too long"); + return PJMEDIA_SDP_ERTPMAPTOOLONG; } - return NULL; + + attr = pj_pool_alloc(pool, sizeof(pjmedia_sdp_attr)); + PJ_ASSERT_RETURN(attr != NULL, PJ_ENOMEM); + + attr->name.ptr = "rtpmap"; + attr->name.slen = 6; + + p = tempbuf; + endbuf = tempbuf+sizeof(tempbuf); + + /* Add payload type. */ + pj_memcpy(p, rtpmap->pt.ptr, rtpmap->pt.slen); + p += rtpmap->pt.slen; + *p++ = ' '; + + /* Add encoding name. */ + for (i=0; i<rtpmap->enc_name.slen; ++i) + p[i] = rtpmap->enc_name.ptr[i]; + p += rtpmap->enc_name.slen; + *p++ = '/'; + + /* Add clock rate. */ + p += pj_utoa(rtpmap->clock_rate, p); + + /* Add parameter if necessary. */ + if (rtpmap->param.slen > 0) { + *p++ = '/'; + for (i=0; i<rtpmap->param.slen; ++i) + p[i] = rtpmap->param.ptr[i]; + p += rtpmap->param.slen; + } + + *p = '\0'; + + attr->value.slen = p-tempbuf; + attr->value.ptr = pj_pool_alloc(pool, attr->value.slen); + pj_memcpy(attr->value.ptr, tempbuf, attr->value.slen); + + *p_attr = attr; + return PJ_SUCCESS; } -static int print_connection_info( pjsdp_conn_info *c, char *buf, int len) + +static int print_connection_info( pjmedia_sdp_conn *c, char *buf, int len) { char *p = buf; @@ -372,9 +458,10 @@ static int print_connection_info( pjsdp_conn_info *c, char *buf, int len) return p-buf; } -PJ_DEF(pjsdp_conn_info*) pjsdp_conn_info_clone (pj_pool_t *pool, const pjsdp_conn_info *rhs) +PJ_DEF(pjmedia_sdp_conn*) pjmedia_sdp_conn_clone (pj_pool_t *pool, + const pjmedia_sdp_conn *rhs) { - pjsdp_conn_info *c = pj_pool_alloc (pool, sizeof(pjsdp_conn_info)); + pjmedia_sdp_conn *c = pj_pool_alloc (pool, sizeof(pjmedia_sdp_conn)); if (!c) return NULL; if (!pj_strdup (pool, &c->net_type, &rhs->net_type)) return NULL; @@ -384,7 +471,31 @@ PJ_DEF(pjsdp_conn_info*) pjsdp_conn_info_clone (pj_pool_t *pool, const pjsdp_con return c; } -static int print_media_desc( pjsdp_media_desc *m, char *buf, int len) +static pj_ssize_t print_attr(const pjmedia_sdp_attr *attr, + char *buf, pj_size_t len) +{ + char *p = buf; + + if ((int)len < attr->name.slen + attr->value.slen + 10) + return -1; + + *p++ = 'a'; + *p++ = '='; + pj_memcpy(p, attr->name.ptr, attr->name.slen); + p += attr->name.slen; + + + if (attr->value.slen) { + pj_memcpy(p, attr->value.ptr, attr->value.slen); + p += attr->value.slen; + } + + *p++ = '\r'; + *p++ = '\n'; + return p-buf; +} + +static int print_media_desc( pjmedia_sdp_media *m, char *buf, int len) { char *p = buf; char *end = buf+len; @@ -439,13 +550,13 @@ static int print_media_desc( pjsdp_media_desc *m, char *buf, int len) return p-buf; } -PJ_DEF(pjsdp_media_desc*) pjsdp_media_desc_clone (pj_pool_t *pool, - const pjsdp_media_desc *rhs) +PJ_DEF(pjmedia_sdp_media*) pjmedia_sdp_media_clone( + pj_pool_t *pool, + const pjmedia_sdp_media *rhs) { unsigned int i; - pjsdp_media_desc *m = pj_pool_alloc (pool, sizeof(pjsdp_media_desc)); - if (!m) - return NULL; + pjmedia_sdp_media *m = pj_pool_alloc (pool, sizeof(pjmedia_sdp_media)); + PJ_ASSERT_RETURN(m != NULL, NULL); pj_strdup (pool, &m->desc.media, &rhs->desc.media); m->desc.port = rhs->desc.port; @@ -456,54 +567,62 @@ PJ_DEF(pjsdp_media_desc*) pjsdp_media_desc_clone (pj_pool_t *pool, m->desc.fmt[i] = rhs->desc.fmt[i]; if (rhs->conn) { - m->conn = pjsdp_conn_info_clone (pool, rhs->conn); - if (!m->conn) - return NULL; + m->conn = pjmedia_sdp_conn_clone (pool, rhs->conn); + PJ_ASSERT_RETURN(m->conn != NULL, NULL); } else { m->conn = NULL; } m->attr_count = rhs->attr_count; for (i=0; i < rhs->attr_count; ++i) { - m->attr[i] = pjsdp_attr_clone (pool, rhs->attr[i]); - if (!m->attr[i]) - return NULL; + m->attr[i] = pjmedia_sdp_attr_clone (pool, rhs->attr[i]); + PJ_ASSERT_RETURN(m->attr[i] != NULL, NULL); } return m; } -/** Check if the media description has the specified attribute. */ -PJ_DEF(pj_bool_t) pjsdp_media_desc_has_attr (const pjsdp_media_desc *m, - pjsdp_attr_type_e attr_type) +PJ_DEF(pjmedia_sdp_attr*) +pjmedia_sdp_media_find_attr(const pjmedia_sdp_media *m, + const pj_str_t *name, const pj_str_t *fmt) { - unsigned i; - for (i=0; i<m->attr_count; ++i) { - pjsdp_attr *attr = m->attr[i]; - if (attr->type == attr_type) - return 1; - } - return 0; + PJ_ASSERT_RETURN(m && name, NULL); + return pjmedia_sdp_attr_find(m->attr_count, m->attr, name, fmt); } -/** Find rtpmap attribute for the specified payload type. */ -PJ_DEF(const pjsdp_rtpmap_attr*) -pjsdp_media_desc_find_rtpmap (const pjsdp_media_desc *m, unsigned pt) + + +PJ_DEF(pjmedia_sdp_attr*) +pjmedia_sdp_media_find_attr2(const pjmedia_sdp_media *m, + const char *name, const pj_str_t *fmt) { - unsigned i; - for (i=0; i<m->attr_count; ++i) { - pjsdp_attr *attr = m->attr[i]; - if (attr->type == PJSDP_ATTR_RTPMAP) { - const pjsdp_rtpmap_attr* rtpmap = (const pjsdp_rtpmap_attr*)attr; - if (rtpmap->payload_type == pt) - return rtpmap; - } - } - return NULL; + PJ_ASSERT_RETURN(m && name, NULL); + return pjmedia_sdp_attr_find2(m->attr_count, m->attr, name, fmt); } -static int print_session(const pjsdp_session_desc *ses, char *buf, pj_ssize_t len) +PJ_DEF(pj_status_t) pjmedia_sdp_media_add_attr( pjmedia_sdp_media *m, + pjmedia_sdp_attr *attr) +{ + return pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); +} + +PJ_DEF(unsigned) +pjmedia_sdp_media_remove_all_attr(pjmedia_sdp_media *m, + const char *name) +{ + return pjmedia_sdp_attr_remove_all(&m->attr_count, m->attr, name); +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_media_remove_attr(pjmedia_sdp_media *m, + pjmedia_sdp_attr *attr) +{ + return pjmedia_sdp_attr_remove(&m->attr_count, m->attr, attr); +} + +static int print_session(const pjmedia_sdp_session *ses, + char *buf, pj_ssize_t len) { char *p = buf; char *end = buf+len; @@ -604,16 +723,21 @@ static int print_session(const pjsdp_session_desc *ses, char *buf, pj_ssize_t le * PARSERS */ -static void parse_version(pj_scanner *scanner) +static void parse_version(pj_scanner *scanner, parse_context *ctx) { + ctx->last_error = PJMEDIA_SDP_EINVER; + pj_scan_advance_n(scanner, 3, SKIP_WS); pj_scan_get_newline(scanner); } -static void parse_origin(pj_scanner *scanner, pjsdp_session_desc *ses) +static void parse_origin(pj_scanner *scanner, pjmedia_sdp_session *ses, + parse_context *ctx) { pj_str_t str; + ctx->last_error = PJMEDIA_SDP_EINORIGIN; + /* o= */ pj_scan_advance_n(scanner, 2, SKIP_WS); @@ -646,10 +770,13 @@ static void parse_origin(pj_scanner *scanner, pjsdp_session_desc *ses) pj_scan_get_newline(scanner); } -static void parse_time(pj_scanner *scanner, pjsdp_session_desc *ses) +static void parse_time(pj_scanner *scanner, pjmedia_sdp_session *ses, + parse_context *ctx) { pj_str_t str; + ctx->last_error = PJMEDIA_SDP_EINTIME; + /* t= */ pj_scan_advance_n(scanner, 2, SKIP_WS); @@ -667,8 +794,11 @@ static void parse_time(pj_scanner *scanner, pjsdp_session_desc *ses) pj_scan_get_newline(scanner); } -static void parse_generic_line(pj_scanner *scanner, pj_str_t *str) +static void parse_generic_line(pj_scanner *scanner, pj_str_t *str, + parse_context *ctx) { + ctx->last_error = PJMEDIA_SDP_EINSDP; + /* x= */ pj_scan_advance_n(scanner, 2, SKIP_WS); @@ -679,8 +809,11 @@ static void parse_generic_line(pj_scanner *scanner, pj_str_t *str) pj_scan_get_newline(scanner); } -static void parse_connection_info(pj_scanner *scanner, pjsdp_conn_info *conn) +static void parse_connection_info(pj_scanner *scanner, pjmedia_sdp_conn *conn, + parse_context *ctx) { + ctx->last_error = PJMEDIA_SDP_EINCONN; + /* c= */ pj_scan_advance_n(scanner, 2, SKIP_WS); @@ -699,10 +832,13 @@ static void parse_connection_info(pj_scanner *scanner, pjsdp_conn_info *conn) pj_scan_get_newline(scanner); } -static void parse_media(pj_scanner *scanner, pjsdp_media_desc *med) +static void parse_media(pj_scanner *scanner, pjmedia_sdp_media *med, + parse_context *ctx) { pj_str_t str; + ctx->last_error = PJMEDIA_SDP_EINMEDIA; + /* m= */ pj_scan_advance_n(scanner, 2, SKIP_WS); @@ -741,152 +877,73 @@ static void parse_media(pj_scanner *scanner, pjsdp_media_desc *med) pj_scan_get_newline(scanner); } -static pjsdp_rtpmap_attr * parse_rtpmap_attr( pj_pool_t *pool, pj_scanner *scanner ) -{ - pjsdp_rtpmap_attr *rtpmap; - pj_str_t str; - - rtpmap = pj_pool_calloc(pool, 1, sizeof(*rtpmap)); - if (pj_scan_get_char(scanner) != ':') { - PJ_THROW(SYNTAX_ERROR); - } - pj_scan_get_until_ch(scanner, ' ', &str); - rtpmap->payload_type = pj_strtoul(&str); - pj_scan_get_char(scanner); - - pj_scan_get_until_ch(scanner, '/', &rtpmap->encoding_name); - pj_scan_get_char(scanner); - pj_scan_get(scanner, &cs_token, &str); - rtpmap->clock_rate = pj_strtoul(&str); - - if (*scanner->curptr == '/') { - pj_scan_get_char(scanner); - pj_scan_get_until_ch(scanner, '\r', &rtpmap->parameter); - } - - return rtpmap; -} - -static pjsdp_attr_string * parse_generic_string_attr( pj_pool_t *pool, pj_scanner *scanner ) -{ - pjsdp_attr_string *attr; - attr = pj_pool_calloc(pool, 1, sizeof(*attr)); - - if (pj_scan_get_char(scanner) != ':') { - PJ_THROW(SYNTAX_ERROR); - } - pj_scan_get_until_ch(scanner, '\r', &attr->value); - return attr; -} - -static pjsdp_attr_num * parse_generic_num_attr( pj_pool_t *pool, pj_scanner *scanner ) -{ - pjsdp_attr_num *attr; - pj_str_t str; - - attr = pj_pool_calloc(pool, 1, sizeof(*attr)); - - if (pj_scan_get_char(scanner) != ':') { - PJ_THROW(SYNTAX_ERROR); - } - pj_scan_get_until_ch(scanner, '\r', &str); - attr->value = pj_strtoul(&str); - return attr; -} - -static pjsdp_attr * parse_name_only_attr( pj_pool_t *pool, pj_scanner *scanner ) +static void on_scanner_error(pj_scanner *scanner) { - pjsdp_attr *attr; - PJ_UNUSED_ARG(scanner); - attr = pj_pool_calloc(pool, 1, sizeof(*attr)); - return attr; + + PJ_THROW(SYNTAX_ERROR); } -static pjsdp_fmtp_attr * parse_fmtp_attr( pj_pool_t *pool, pj_scanner *scanner ) +static pjmedia_sdp_attr *parse_attr( pj_pool_t *pool, pj_scanner *scanner, + parse_context *ctx) { - pjsdp_fmtp_attr *fmtp; + pjmedia_sdp_attr *attr; - fmtp = pj_pool_calloc(pool, 1, sizeof(*fmtp)); - - if (pj_scan_get_char(scanner) != ':') { - PJ_THROW(SYNTAX_ERROR); - } - pj_scan_get_until_ch(scanner, ' ', &fmtp->format); - pj_scan_get_char(scanner); - pj_scan_get_until_ch(scanner, '\r', &fmtp->param); - return fmtp; -} + ctx->last_error = PJMEDIA_SDP_EINATTR; -static pjsdp_attr *parse_attr( pj_pool_t *pool, pj_scanner *scanner) -{ - void * (*parse_func)(pj_pool_t *pool, pj_scanner *scanner) = NULL; - pj_str_t attrname; - unsigned i; - pjsdp_attr *attr; + attr = pj_pool_alloc(pool, sizeof(pjmedia_sdp_attr)); /* skip a= */ pj_scan_advance_n(scanner, 2, SKIP_WS); /* get attr name. */ - pj_scan_get(scanner, &cs_token, &attrname); - - /* find entry to handle attrname */ - for (i=0; i<PJ_ARRAY_SIZE(attr_map); ++i) { - struct attr_map_rec *p = &attr_map[i]; - if (pj_strcmp(&attrname, &p->name) == 0) { - parse_func = p->parse_attr; - break; - } - } + pj_scan_get(scanner, &cs_token, &attr->name); - /* fallback to generic string parser. */ - if (parse_func == NULL) { - parse_func = &parse_generic_string_attr; + if (*scanner->curptr != '\r' && *scanner->curptr != '\n') { + /* get value */ + pj_scan_get_until_ch(scanner, '\r', &attr->value); + } else { + attr->value.ptr = NULL; + attr->value.slen = 0; } - attr = (*parse_func)(pool, scanner); - attr->type = i; - /* newline */ pj_scan_get_newline(scanner); return attr; } -static void on_scanner_error(pj_scanner *scanner) -{ - PJ_UNUSED_ARG(scanner); - - PJ_THROW(SYNTAX_ERROR); -} - /* * Parse SDP message. */ -PJ_DEF(pjsdp_session_desc*) pjsdp_parse( char *buf, pj_size_t len, - pj_pool_t *pool) +PJ_DEF(pj_status_t) pjmedia_sdp_parse( pj_pool_t *pool, + char *buf, pj_size_t len, + pjmedia_sdp_session **p_sdp) { pj_scanner scanner; - pjsdp_session_desc *session; - pjsdp_media_desc *media = NULL; + pjmedia_sdp_session *session; + pjmedia_sdp_media *media = NULL; void *attr; - pjsdp_conn_info *conn; + pjmedia_sdp_conn *conn; pj_str_t dummy; int cur_name = 254; + parse_context ctx; PJ_USE_EXCEPTION; + ctx.last_error = PJ_SUCCESS; + init_sdp_parser(); pj_scan_init(&scanner, buf, len, 0, &on_scanner_error); - session = pj_pool_calloc(pool, 1, sizeof(*session)); + session = pj_pool_calloc(pool, 1, sizeof(pjmedia_sdp_session)); + PJ_ASSERT_RETURN(session != NULL, PJ_ENOMEM); PJ_TRY { while (!pj_scan_is_eof(&scanner)) { cur_name = *scanner.curptr; switch (cur_name) { case 'a': - attr = parse_attr(pool, &scanner); + attr = parse_attr(pool, &scanner, &ctx); if (attr) { if (media) { media->attr[media->attr_count++] = attr; @@ -896,14 +953,14 @@ PJ_DEF(pjsdp_session_desc*) pjsdp_parse( char *buf, pj_size_t len, } break; case 'o': - parse_origin(&scanner, session); + parse_origin(&scanner, session, &ctx); break; case 's': - parse_generic_line(&scanner, &session->name); + parse_generic_line(&scanner, &session->name, &ctx); break; case 'c': conn = pj_pool_calloc(pool, 1, sizeof(*conn)); - parse_connection_info(&scanner, conn); + parse_connection_info(&scanner, conn, &ctx); if (media) { media->conn = conn; } else { @@ -911,44 +968,201 @@ PJ_DEF(pjsdp_session_desc*) pjsdp_parse( char *buf, pj_size_t len, } break; case 't': - parse_time(&scanner, session); + parse_time(&scanner, session, &ctx); break; case 'm': media = pj_pool_calloc(pool, 1, sizeof(*media)); - parse_media(&scanner, media); + parse_media(&scanner, media, &ctx); session->media[ session->media_count++ ] = media; break; case 'v': - parse_version(&scanner); + parse_version(&scanner, &ctx); break; default: - parse_generic_line(&scanner, &dummy); + parse_generic_line(&scanner, &dummy, &ctx); break; } } + + ctx.last_error = PJ_SUCCESS; + } PJ_CATCH(SYNTAX_ERROR) { - PJ_LOG(2, (LOG_THIS, "Syntax error in SDP parser '%c' line %d col %d", - cur_name, scanner.line, pj_scan_get_col(&scanner))); - if (!pj_scan_is_eof(&scanner)) { - if (*scanner.curptr != '\r') { - pj_scan_get_until_ch(&scanner, '\r', &dummy); - } - pj_scan_get_newline(&scanner); - } + + char errmsg[PJMEDIA_ERR_MSG_SIZE]; + pjmedia_strerror(ctx.last_error, errmsg, sizeof(errmsg)); + + PJ_LOG(4, (THIS_FILE, "Error parsing SDP in line %d col %d: %s", + scanner.line, pj_scan_get_col(&scanner), + errmsg)); + + session = NULL; + } PJ_END; pj_scan_fini(&scanner); - return session; + + *p_sdp = session; + return ctx.last_error; } /* * Print SDP description. */ -PJ_DEF(int) pjsdp_print( const pjsdp_session_desc *desc, char *buf, pj_size_t size) +PJ_DEF(int) pjmedia_sdp_print( const pjmedia_sdp_session *desc, + char *buf, pj_size_t size) { return print_session(desc, buf, size); } +/* + * Clone session + */ +PJ_DEF(pjmedia_sdp_session*) +pjmedia_sdp_session_clone( pj_pool_t *pool, + const pjmedia_sdp_session *rhs) +{ + pjmedia_sdp_session *sess; + unsigned i; + + PJ_ASSERT_RETURN(pool && rhs, NULL); + + sess = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_session)); + PJ_ASSERT_RETURN(sess != NULL, NULL); + + /* Clone origin line. */ + pj_strdup(pool, &sess->origin.user, &rhs->origin.user); + sess->origin.id = rhs->origin.id; + sess->origin.version = rhs->origin.version; + pj_strdup(pool, &sess->origin.net_type, &rhs->origin.net_type); + pj_strdup(pool, &sess->origin.addr_type, &rhs->origin.addr_type); + pj_strdup(pool, &sess->origin.addr, &rhs->origin.addr); + + /* Clone subject line. */ + pj_strdup(pool, &sess->name, &rhs->name); + + /* Clone connection line */ + if (rhs->conn) { + sess->conn = pjmedia_sdp_conn_clone(pool, rhs->conn); + PJ_ASSERT_RETURN(sess->conn != NULL, NULL); + } + + /* Clone time line. */ + sess->time.start = rhs->time.start; + sess->time.stop = rhs->time.stop; + + /* Duplicate session attributes. */ + sess->attr_count = rhs->attr_count; + for (i=0; i<rhs->attr_count; ++i) { + sess->attr[i] = pjmedia_sdp_attr_clone(pool, rhs->attr[i]); + } + + /* Duplicate media descriptors. */ + sess->media_count = rhs->media_count; + for (i=0; i<rhs->media_count; ++i) { + sess->media[i] = pjmedia_sdp_media_clone(pool, rhs->media[i]); + } + + return sess; +} + + +#define CHECK(exp,ret) do { \ + pj_assert(exp); \ + if (!(exp)) \ + return ret; \ + } while (0) + +/* Validate SDP connetion info. */ +static pj_status_t validate_sdp_conn(const pjmedia_sdp_conn *c) +{ + CHECK( c, PJ_EINVAL); + CHECK( pj_strcmp2(&c->net_type, "IN")==0, PJMEDIA_SDP_EINCONN); + CHECK( pj_strcmp2(&c->addr_type, "IP4")==0 || + pj_strcmp2(&c->addr_type, "IP6")==0, + PJMEDIA_SDP_EINCONN); + CHECK( c->addr.slen != 0, PJMEDIA_SDP_EINCONN); + + return PJ_SUCCESS; +} + + +/* Validate SDP session descriptor. */ +PJ_DEF(pj_status_t) pjmedia_sdp_validate(const pjmedia_sdp_session *sdp) +{ + unsigned i; + + CHECK( sdp != NULL, PJ_EINVAL); + + /* Validate origin line. */ + CHECK( sdp->origin.user.slen != 0, PJMEDIA_SDP_EINORIGIN); + CHECK( pj_strcmp2(&sdp->origin.net_type, "IN")==0, + PJMEDIA_SDP_EINORIGIN); + CHECK( pj_strcmp2(&sdp->origin.addr_type, "IP4")==0 || + pj_strcmp2(&sdp->origin.addr_type, "IP6")==0, + PJMEDIA_SDP_EINORIGIN); + CHECK( sdp->origin.addr.slen != 0, PJMEDIA_SDP_EINORIGIN); + + /* Validate subject line. */ + CHECK( sdp->name.slen != 0, PJMEDIA_SDP_EINNAME); + + /* Ignore start and stop time. */ + + /* If session level connection info is present, validate it. */ + if (sdp->conn) { + pj_status_t status = validate_sdp_conn(sdp->conn); + if (status != PJ_SUCCESS) + return status; + } + + /* Validate each media. */ + for (i=0; i<sdp->media_count; ++i) { + const pjmedia_sdp_media *m = sdp->media[i]; + unsigned j; + + /* Validate the m= line. */ + CHECK( m->desc.media.slen != 0, PJMEDIA_SDP_EINMEDIA); + CHECK( m->desc.transport.slen != 0, PJMEDIA_SDP_EINMEDIA); + CHECK( m->desc.fmt_count != 0, PJMEDIA_SDP_ENOFMT); + + /* If media level connection info is present, validate it. */ + if (m->conn) { + pj_status_t status = validate_sdp_conn(m->conn); + if (status != PJ_SUCCESS) + return status; + } + + /* If media doesn't have connection info, then connection info + * must be present in the session. + */ + if (m->conn == NULL) { + if (sdp->conn == NULL) + return PJMEDIA_SDP_EMISSINGCONN; + } + + /* Verify payload type. */ + for (j=0; j<m->desc.fmt_count; ++j) { + unsigned pt = pj_strtoul(&m->desc.fmt[j]); + + /* Payload type is between 0 and 127. */ + CHECK( pt <= 127, PJMEDIA_SDP_EINPT); + + /* If port is not zero, then for each dynamic payload type, an + * rtpmap attribute must be specified. + */ + if (m->desc.port != 0 && pt >= 96) { + const pjmedia_sdp_attr *a; + + a = pjmedia_sdp_media_find_attr2(m, "rtpmap", &m->desc.fmt[j]); + CHECK( a != NULL, PJMEDIA_SDP_EMISSINGRTPMAP); + } + } + } + + /* Looks good. */ + return PJ_SUCCESS; +} + + diff --git a/pjmedia/src/pjmedia/sdp_cmp.c b/pjmedia/src/pjmedia/sdp_cmp.c new file mode 100644 index 00000000..f8817445 --- /dev/null +++ b/pjmedia/src/pjmedia/sdp_cmp.c @@ -0,0 +1,292 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia/sdp.h> +#include <pjmedia/errno.h> +#include <pj/assert.h> +#include <pj/string.h> + + +/* Compare connection line. */ +static pj_status_t compare_conn(const pjmedia_sdp_conn *c1, + const pjmedia_sdp_conn *c2) +{ + /* Compare network type. */ + if (pj_strcmp(&c1->net_type, &c2->net_type) != 0) + return PJMEDIA_SDP_ECONNNOTEQUAL; + + /* Compare address type. */ + if (pj_strcmp(&c1->addr_type, &c2->addr_type) != 0) + return PJMEDIA_SDP_ECONNNOTEQUAL; + + /* Compare address. */ + if (pj_strcmp(&c1->addr, &c2->addr) != 0) + return PJMEDIA_SDP_ECONNNOTEQUAL; + + return PJ_SUCCESS; +} + +/* Compare attributes array. */ +static pj_status_t compare_attr_imp(unsigned count1, + const pjmedia_sdp_attr *const attr1[], + unsigned count2, + const pjmedia_sdp_attr *const attr2[]) +{ + pj_status_t status; + unsigned i; + const pj_str_t inactive = { "inactive", 8 }; + const pj_str_t sendrecv = { "sendrecv", 8 }; + const pj_str_t sendonly = { "sendonly", 8 }; + const pj_str_t recvonly = { "recvonly", 8 }; + const pj_str_t fmtp = { "fmtp", 4 }; + const pj_str_t rtpmap = { "rtpmap", 6 }; + + /* For simplicity, we only compare the following attributes, and ignore + * the others: + * - direction, eg. inactive, sendonly, recvonly, sendrecv + * - fmtp for each payload. + * - rtpmap for each payload. + */ + for (i=0; i<count1; ++i) { + const pjmedia_sdp_attr *a1 = attr1[i]; + + if (pj_strcmp(&a1->name, &inactive) == 0 || + pj_strcmp(&a1->name, &sendrecv) == 0 || + pj_strcmp(&a1->name, &sendonly) == 0 || + pj_strcmp(&a1->name, &recvonly) == 0) + { + /* For inactive, sendrecv, sendonly, and recvonly attributes, + * the same attribute must be present on the other SDP. + */ + const pjmedia_sdp_attr *a2; + a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, NULL); + if (!a2) + return PJMEDIA_SDP_EDIRNOTEQUAL; + + } else if (pj_strcmp(&a1->name, &fmtp) == 0) { + /* For fmtp attribute, find the fmtp attribute in the other SDP + * for the same payload type, and compare the fmtp param/value. + */ + pjmedia_sdp_fmtp fmtp1, fmtp2; + const pjmedia_sdp_attr *a2; + + status = pjmedia_sdp_attr_get_fmtp(a1, &fmtp1); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_EFMTPNOTEQUAL; + + a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, &fmtp1.fmt); + if (!a2) + return PJMEDIA_SDP_EFMTPNOTEQUAL; + + status = pjmedia_sdp_attr_get_fmtp(a2, &fmtp2); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_EFMTPNOTEQUAL; + + if (pj_strcmp(&fmtp1.fmt_param, &fmtp2.fmt_param) != 0) + return PJMEDIA_SDP_EFMTPNOTEQUAL; + + } else if (pj_strcmp(&a1->name, &rtpmap) == 0) { + /* For rtpmap attribute, find rtpmap attribute on the other SDP + * for the same payload type, and compare both rtpmap atribute + * values. + */ + pjmedia_sdp_rtpmap r1, r2; + const pjmedia_sdp_attr *a2; + + status = pjmedia_sdp_attr_get_rtpmap(a1, &r1); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + + a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, &r1.pt); + if (!a2) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + + status = pjmedia_sdp_attr_get_rtpmap(a2, &r2); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + + if (pj_strcmp(&r1.pt, &r2.pt) != 0) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + if (pj_strcmp(&r1.enc_name, &r2.enc_name) != 0) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + if (r1.clock_rate != r2.clock_rate) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + if (pj_strcmp(&r1.param, &r2.param) != 0) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + } + } + + return PJ_SUCCESS; +} + + +/* Compare attributes array. */ +static pj_status_t compare_attr(unsigned count1, + const pjmedia_sdp_attr *const attr1[], + unsigned count2, + const pjmedia_sdp_attr *const attr2[]) +{ + pj_status_t status; + + status = compare_attr_imp(count1, attr1, count2, attr2); + if (status != PJ_SUCCESS) + return status; + + status = compare_attr_imp(count2, attr2, count1, attr1); + if (status != PJ_SUCCESS) + return status; + + return PJ_SUCCESS; +} + +/* Compare media descriptor */ +PJ_DEF(pj_status_t) pjmedia_sdp_media_cmp( const pjmedia_sdp_media *sd1, + const pjmedia_sdp_media *sd2, + unsigned option) +{ + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(sd1 && sd2 && option==0, PJ_EINVAL); + + PJ_UNUSED_ARG(option); + + /* Compare media type. */ + if (pj_strcmp(&sd1->desc.media, &sd2->desc.media) != 0) + return PJMEDIA_SDP_EMEDIANOTEQUAL; + + /* Compare port number. */ + if (sd1->desc.port != sd2->desc.port) + return PJMEDIA_SDP_EPORTNOTEQUAL; + + /* Compare port count. */ + if (sd1->desc.port_count != sd2->desc.port_count) + return PJMEDIA_SDP_EPORTNOTEQUAL; + + /* Compare transports. */ + if (pj_strcmp(&sd1->desc.transport, &sd2->desc.transport) != 0) + return PJMEDIA_SDP_ETPORTNOTEQUAL; + + /* Compare number of formats. */ + if (sd1->desc.fmt_count != sd2->desc.fmt_count) + return PJMEDIA_SDP_EFORMATNOTEQUAL; + + /* Compare formats, in order. */ + for (i=0; i<sd1->desc.fmt_count; ++i) { + if (pj_strcmp(&sd1->desc.fmt[i], &sd2->desc.fmt[i]) != 0) + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } + + /* Compare connection line, if they exist. */ + if (sd1->conn) { + if (!sd2->conn) + return PJMEDIA_SDP_EMEDIANOTEQUAL; + status = compare_conn(sd1->conn, sd2->conn); + } else { + if (sd2->conn) + return PJMEDIA_SDP_EMEDIANOTEQUAL; + } + + /* Compare attributes. */ + status = compare_attr(sd1->attr_count, sd1->attr, + sd2->attr_count, sd2->attr); + if (status != PJ_SUCCESS) + return status; + + /* Looks equal */ + return PJ_SUCCESS; +} + +/* + * Compare two SDP session for equality. + */ +PJ_DEF(pj_status_t) pjmedia_sdp_session_cmp( const pjmedia_sdp_session *sd1, + const pjmedia_sdp_session *sd2, + unsigned option) +{ + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(sd1 && sd2 && option==0, PJ_EINVAL); + + PJ_UNUSED_ARG(option); + + /* Compare the origin line. */ + if (pj_strcmp(&sd1->origin.user, &sd2->origin.user) != 0) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (sd1->origin.id != sd2->origin.id) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (sd1->origin.version != sd2->origin.version) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (pj_strcmp(&sd1->origin.net_type, &sd2->origin.net_type) != 0) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (pj_strcmp(&sd1->origin.addr_type, &sd2->origin.addr_type) != 0) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (pj_strcmp(&sd1->origin.addr, &sd2->origin.addr) != 0) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + + /* Compare the subject line. */ + if (pj_strcmp(&sd1->name, &sd2->name) != 0) + return PJMEDIA_SDP_ENAMENOTEQUAL; + + /* Compare connection line, when they exist */ + if (sd1->conn) { + if (!sd2->conn) + return PJMEDIA_SDP_ECONNNOTEQUAL; + status = compare_conn(sd1->conn, sd2->conn); + if (status != PJ_SUCCESS) + return status; + } else { + if (sd2->conn) + return PJMEDIA_SDP_ECONNNOTEQUAL; + } + + /* Compare time line. */ + if (sd1->time.start != sd2->time.start) + return PJMEDIA_SDP_ETIMENOTEQUAL; + + if (sd1->time.stop != sd2->time.stop) + return PJMEDIA_SDP_ETIMENOTEQUAL; + + /* Compare attributes. */ + status = compare_attr(sd1->attr_count, sd1->attr, + sd2->attr_count, sd2->attr); + if (status != PJ_SUCCESS) + return status; + + /* Compare media lines. */ + if (sd1->media_count != sd2->media_count) + return PJMEDIA_SDP_EMEDIANOTEQUAL; + + for (i=0; i<sd1->media_count; ++i) { + status = pjmedia_sdp_media_cmp(sd1->media[i], sd2->media[i], 0); + if (status != PJ_SUCCESS) + return status; + } + + /* Looks equal. */ + return PJ_SUCCESS; +} + + diff --git a/pjmedia/src/pjmedia/sdp_neg.c b/pjmedia/src/pjmedia/sdp_neg.c new file mode 100644 index 00000000..313fff99 --- /dev/null +++ b/pjmedia/src/pjmedia/sdp_neg.c @@ -0,0 +1,815 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia/sdp_neg.h> +#include <pjmedia/sdp.h> +#include <pjmedia/errno.h> +#include <pj/assert.h> +#include <pj/pool.h> +#include <pj/string.h> +#include <pj/ctype.h> +#include <pj/array.h> + +/** + * This structure describes SDP media negotiator. + */ +struct pjmedia_sdp_neg +{ + pjmedia_sdp_neg_state state; /**< Negotiator state. */ + pj_bool_t has_remote_answer; + + pjmedia_sdp_session *initial_sdp, /**< Initial local SDP */ + *active_local_sdp, /**< Currently active local SDP. */ + *active_remote_sdp, /**< Currently active remote's. */ + *neg_local_sdp, /**< Temporary local SDP. */ + *neg_remote_sdp; /**< Temporary remote SDP. */ +}; + + +/* + * Create with local offer. + */ +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_create_w_local_offer( pj_pool_t *pool, + const pjmedia_sdp_session *local, + pjmedia_sdp_neg **p_neg) +{ + pjmedia_sdp_neg *neg; + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && local && p_neg, PJ_EINVAL); + + *p_neg = NULL; + + /* Validate local offer. */ + PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(local))==PJ_SUCCESS, status); + + /* Create and initialize negotiator. */ + neg = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_neg)); + PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM); + + neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, local); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); + + *p_neg = neg; + return PJ_SUCCESS; +} + +/* + * Create with remote offer and initial local offer/answer. + */ +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool, + const pjmedia_sdp_session *local, + const pjmedia_sdp_session *remote, + pjmedia_sdp_neg **p_neg) +{ + pjmedia_sdp_neg *neg; + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && local && remote && p_neg, PJ_EINVAL); + + *p_neg = NULL; + + /* Validate remote offer and local answer */ + status = pjmedia_sdp_validate(remote); + if (status != PJ_SUCCESS) + return status; + PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(local))==PJ_SUCCESS, status); + + /* Create and initialize negotiator. */ + neg = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_neg)); + PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM); + + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, local); + PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(neg->initial_sdp))==PJ_SUCCESS, + status); + + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(neg->neg_remote_sdp))==PJ_SUCCESS, + status); + + *p_neg = neg; + return PJ_SUCCESS; +} + +/* + * Get SDP negotiator state. + */ +PJ_DEF(pjmedia_sdp_neg_state) +pjmedia_sdp_neg_get_state( pjmedia_sdp_neg *neg ) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(neg != NULL, PJMEDIA_SDP_NEG_STATE_NULL); + return neg->state; +} + + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_get_local( pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **local) +{ + PJ_ASSERT_RETURN(neg && local, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE); + + *local = neg->active_local_sdp; + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_get_remote( pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **remote) +{ + PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->active_remote_sdp, PJMEDIA_SDPNEG_ENOACTIVE); + + *remote = neg->active_remote_sdp; + return PJ_SUCCESS; +} + + +/* + * Modify local SDP and wait for remote answer. + */ +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_modify_local_offer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session *local) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL); + + /* Can only do this in STATE_DONE. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE, + PJMEDIA_SDPNEG_EINSTATE); + + /* Change state to STATE_LOCAL_OFFER */ + neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, local); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_tx_local_offer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **offer) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(neg && offer, PJ_EINVAL); + + *offer = NULL; + + /* Can only do this in STATE_DONE or STATE_LOCAL_OFFER. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE || + neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + PJMEDIA_SDPNEG_EINSTATE); + + if (neg->state == PJMEDIA_SDP_NEG_STATE_DONE) { + /* If in STATE_DONE, set the active SDP as the offer. */ + PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE); + + neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, + neg->active_local_sdp); + *offer = neg->active_local_sdp; + + } else { + /* We assume that we're in STATE_LOCAL_OFFER. + * In this case set the neg_local_sdp as the offer. + */ + *offer = neg->neg_local_sdp; + } + + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_rx_remote_offer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session *remote) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL); + + /* Can only do this in STATE_DONE. + * If we already provide local offer, then rx_remote_answer() should + * be called instead of this function. + */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE, + PJMEDIA_SDPNEG_EINSTATE); + + /* We're ready to negotiate. */ + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_rx_remote_answer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session *remote) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL); + + /* Can only do this in STATE_LOCAL_OFFER. + * If we haven't provided local offer, then rx_remote_offer() should + * be called instead of this function. + */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + PJMEDIA_SDPNEG_EINSTATE); + + /* We're ready to negotiate. */ + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + neg->has_remote_answer = 1; + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + + return PJ_SUCCESS; +} + +/* Swap string. */ +static void str_swap(pj_str_t *str1, pj_str_t *str2) +{ + pj_str_t tmp = *str1; + *str1 = *str2; + *str2 = tmp; +} + +static void remove_all_media_directions(pjmedia_sdp_media *m) +{ + pjmedia_sdp_media_remove_all_attr(m, "inactive"); + pjmedia_sdp_media_remove_all_attr(m, "sendrecv"); + pjmedia_sdp_media_remove_all_attr(m, "sendonly"); + pjmedia_sdp_media_remove_all_attr(m, "recvonly"); +} + +/* Update media direction based on peer's media direction */ +static void update_media_direction(pj_pool_t *pool, + const pjmedia_sdp_media *remote, + pjmedia_sdp_media *local) +{ + if (pjmedia_sdp_media_find_attr2(remote, "inactive", NULL) != NULL) { + /* If remote has "a=inactive", then local is inactive too */ + + pjmedia_sdp_attr *a; + + remove_all_media_directions(local); + + a = pjmedia_sdp_attr_create(pool, "inactive", NULL); + pjmedia_sdp_media_add_attr(local, a); + + } else if (pjmedia_sdp_media_find_attr2(remote, "sendonly", NULL) != NULL) { + /* If remote has "a=sendonly", then set local to "recvonly" if + * it is currently "sendrecv". + */ + + pjmedia_sdp_attr *a; + + remove_all_media_directions(local); + + a = pjmedia_sdp_attr_create(pool, "recvonly", NULL); + pjmedia_sdp_media_add_attr(local, a); + + } else if (pjmedia_sdp_media_find_attr2(remote, "recvonly", NULL) != NULL) { + /* If remote has "a=recvonly", then set local to "sendonly" if + * it is currently "sendrecv". + */ + + pjmedia_sdp_attr *a; + + remove_all_media_directions(local); + + a = pjmedia_sdp_attr_create(pool, "sendonly", NULL); + pjmedia_sdp_media_add_attr(local, a); + + } else if (pjmedia_sdp_media_find_attr2(remote, "sendrecv", NULL) != NULL) { + + pjmedia_sdp_attr *a; + + remove_all_media_directions(local); + + a = pjmedia_sdp_attr_create(pool, "sendrecv", NULL); + pjmedia_sdp_media_add_attr(local, a); + + } else { + /* Otherwise remote is set to "sendrecv". + */ + remove_all_media_directions(local); + } +} + +/* Update single local media description to after receiving answer + * from remote. + */ +static pj_status_t process_m_answer( pj_pool_t *pool, + pjmedia_sdp_media *offer, + pjmedia_sdp_media *answer, + pj_bool_t allow_asym) +{ + unsigned i; + + /* Check that the media type match our offer. */ + + if (pj_strcmp(&answer->desc.media, &offer->desc.media)!=0) { + /* The media type in the answer is different than the offer! */ + return PJMEDIA_SDPNEG_EINVANSMEDIA; + } + + + /* Chec that transport in the answer match our offer. */ + + if (pj_strcmp(&answer->desc.transport, + &offer->desc.transport)!=0) + { + /* The transport in the answer is different than the offer! */ + return PJMEDIA_SDPNEG_EINVANSTP; + } + + /* Check if remote has rejected our offer */ + + if (answer->desc.port == 0) { + + /* Remote has rejected our offer. + * Set our port to zero too in active SDP. + */ + offer->desc.port = 0; + } + + + /* Process direction attributes */ + update_media_direction(pool, answer, offer); + + /* If asymetric media is allowed, then just check that remote answer has + * codecs that are within the offer. + * + * Otherwise if asymetric media is not allowed, then we will choose only + * one codec in our initial offer to match the answer. + */ + if (allow_asym) { + for (i=0; i<answer->desc.fmt_count; ++i) { + unsigned j; + pj_str_t *rem_fmt = &answer->desc.fmt[i]; + + for (j=0; j<offer->desc.fmt_count; ++j) { + if (pj_strcmp(rem_fmt, &answer->desc.fmt[j])==0) + break; + } + + if (j != offer->desc.fmt_count) { + /* Found at least one common codec. */ + break; + } + } + + if (i == answer->desc.fmt_count) { + /* No common codec in the answer! */ + return PJMEDIA_SDPNEG_EANSNOMEDIA; + } + + PJ_TODO(CHECK_SDP_NEGOTIATION_WHEN_ASYMETRIC_MEDIA_IS_ALLOWED); + + } else { + /* Remove all format in the offer that has no matching answer */ + for (i=0; i<offer->desc.fmt_count;) { + unsigned j; + pj_str_t *fmt = &offer->desc.fmt[i]; + + for (j=0; j<answer->desc.fmt_count; ++j) { + if (pj_strcmp(fmt, &answer->desc.fmt[j])==0) + break; + } + + if (j == answer->desc.fmt_count) { + /* This format has no matching answer. + * Remove it from our offer. + */ + pjmedia_sdp_attr *a; + + /* Remove rtpmap associated with this format */ + a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt); + if (a) + pjmedia_sdp_media_remove_attr(offer, a); + + /* Remove fmtp associated with this format */ + a = pjmedia_sdp_media_find_attr2(offer, "fmtp", fmt); + if (a) + pjmedia_sdp_media_remove_attr(offer, a); + + /* Remove this format from offer's array */ + pj_array_erase(offer->desc.fmt, sizeof(offer->desc.fmt[0]), + offer->desc.fmt_count, i); + --offer->desc.fmt_count; + + } else { + ++i; + } + } + + /* Arrange format in the offer so the order match the priority + * in the answer + */ + for (i=0; i<answer->desc.fmt_count; ++i) { + unsigned j; + pj_str_t *fmt = &answer->desc.fmt[i]; + + for (j=i; j<offer->desc.fmt_count; ++j) { + if (pj_strcmp(fmt, &offer->desc.fmt[j])==0) { + str_swap(&offer->desc.fmt[i], &offer->desc.fmt[j]); + break; + } + } + } + } + + /* Looks okay */ + return PJ_SUCCESS; +} + + +/* Update local media session (offer) to create active local session + * after receiving remote answer. + */ +static pj_status_t process_answer(pj_pool_t *pool, + pjmedia_sdp_session *offer, + pjmedia_sdp_session *answer, + pj_bool_t allow_asym, + pjmedia_sdp_session **p_active) +{ + unsigned mi; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(pool && offer && answer && p_active, PJ_EINVAL); + + /* Check that media count match between offer and answer */ + if (offer->media_count != answer->media_count) + return PJMEDIA_SDPNEG_EMISMEDIA; + + /* Now update each media line in the offer with the answer. */ + for (mi=0; mi<offer->media_count; ++mi) { + status = process_m_answer(pool, offer->media[mi], answer->media[mi], + allow_asym); + if (status != PJ_SUCCESS) + return status; + } + + *p_active = offer; + return PJ_SUCCESS; +} + +/* Try to match offer with answer. */ +static pj_bool_t match_offer(pj_pool_t *pool, + const pjmedia_sdp_media *offer, + const pjmedia_sdp_media *local, + pjmedia_sdp_media **p_answer) +{ + unsigned i; + pj_bool_t offer_has_codec = 0, + offer_has_telephone_event = 0, + offer_has_other = 0, + found_matching_codec = 0, + found_matching_telephone_event = 0, + found_matching_other = 0; + unsigned pt_answer_count = 0; + pj_str_t pt_answer[PJSDP_MAX_FMT]; + pjmedia_sdp_media *answer; + + /* With the addition of telephone-event and dodgy MS RTC SDP, + * the answer generation algorithm looks really shitty... + */ + for (i=0; i<offer->desc.fmt_count; ++i) { + unsigned j; + + if (pj_isdigit(*offer->desc.fmt[i].ptr)) { + /* This is normal/standard payload type, where it's identified + * by payload number. + */ + unsigned pt; + + pt = pj_strtoul(&offer->desc.fmt[i]); + + if (pt < 96) { + /* For static payload type, it's enough to compare just + * the payload number. + */ + + offer_has_codec = 1; + + /* We just need to select one codec. + * Continue if we have selected matching codec for previous + * payload. + */ + if (found_matching_codec) + continue; + + /* Find matching codec in local descriptor. */ + for (j=0; j<local->desc.fmt_count; ++j) { + unsigned p; + p = pj_strtoul(&local->desc.fmt[j]); + if (p == pt && pj_isdigit(*local->desc.fmt[j].ptr)) { + found_matching_codec = 1; + pt_answer[pt_answer_count++] = local->desc.fmt[j]; + break; + } + } + + } else { + /* This is dynamic payload type. + * For dynamic payload type, we must look the rtpmap and + * compare the encoding name. + */ + const pjmedia_sdp_attr *a; + pjmedia_sdp_rtpmap or; + pj_bool_t is_codec; + + /* Get the rtpmap for the payload type in the offer. */ + a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", &offer->desc.fmt[i]); + if (!a) { + pj_assert(!"Bug! Offer should have been validated"); + return PJ_FALSE; + } + pjmedia_sdp_attr_get_rtpmap(a, &or); + + if (!pj_strcmp2(&or.enc_name, "telephone-event")) { + offer_has_telephone_event = 1; + if (found_matching_telephone_event) + continue; + is_codec = 0; + } else { + offer_has_codec = 1; + if (found_matching_codec) + continue; + is_codec = 1; + } + + /* Find paylaod in our initial SDP with matching + * encoding name. + */ + for (j=0; j<local->desc.fmt_count; ++j) { + a = pjmedia_sdp_media_find_attr2(local, "rtpmap", &local->desc.fmt[j]); + if (a) { + pjmedia_sdp_rtpmap lr; + pjmedia_sdp_attr_get_rtpmap(a, &lr); + if (!pj_strcmp(&or.enc_name, &lr.enc_name)) { + /* Match! */ + if (is_codec) + found_matching_codec = 1; + else + found_matching_telephone_event = 1; + pt_answer[pt_answer_count++] = local->desc.fmt[j]; + break; + } + } + } + } + + } else { + /* This is a non-standard, brain damaged SDP where the payload + * type is non-numeric. It exists e.g. in Microsoft RTC based + * UA, to indicate instant messaging capability. + * Example: + * - m=x-ms-message 5060 sip null + */ + offer_has_other = 1; + if (found_matching_other) + continue; + + for (j=0; j<local->desc.fmt_count; ++j) { + if (!pj_strcmp(&offer->desc.fmt[i], &local->desc.fmt[j])) { + /* Match */ + found_matching_other = 1; + pt_answer[pt_answer_count++] = local->desc.fmt[j]; + break; + } + } + } + } + + /* See if all types of offer can be matched. */ + if ((offer_has_codec && !found_matching_codec) || + (offer_has_telephone_event && !found_matching_telephone_event) || + (offer_has_other && !found_matching_other)) + { + /* Some of the payload in the offer has no matching local sdp */ + return PJ_FALSE; + } + + /* Seems like everything is in order. + * Build the answer by cloning from local media, but rearrange the payload + * to suit the offer. + */ + answer = pjmedia_sdp_media_clone(pool, local); + for (i=0; i<pt_answer_count; ++i) { + unsigned j; + for (j=i; j<answer->desc.fmt_count; ++j) { + if (!pj_strcmp(&answer->desc.fmt[j], &pt_answer[i])) + break; + } + pj_assert(j != answer->desc.fmt_count); + str_swap(&answer->desc.fmt[i], &answer->desc.fmt[j]); + } + + /* Remove unwanted local formats. */ + for (i=pt_answer_count; i<answer->desc.fmt_count; ++i) { + pjmedia_sdp_attr *a; + + /* Remove rtpmap for this format */ + a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", + &answer->desc.fmt[i]); + if (a) { + pjmedia_sdp_media_remove_attr(answer, a); + } + + /* Remove fmtp for this format */ + a = pjmedia_sdp_media_find_attr2(answer, "fmtp", + &answer->desc.fmt[i]); + if (a) { + pjmedia_sdp_media_remove_attr(answer, a); + } + } + answer->desc.fmt_count = pt_answer_count; + + /* If offer has zero port, set our answer with zero port too */ + if (offer->desc.port==0) + answer->desc.port = 0; + + /* Update media direction. */ + update_media_direction(pool, offer, answer); + + *p_answer = answer; + return PJ_TRUE; +} + +/* Create complete answer for remote's offer. */ +static pj_status_t create_answer( pj_pool_t *pool, + const pjmedia_sdp_session *initial, + const pjmedia_sdp_session *offer, + pjmedia_sdp_session **p_answer) +{ + pj_status_t status; + pjmedia_sdp_session *answer; + char media_used[PJSDP_MAX_MEDIA]; + unsigned i; + + /* Validate remote offer. + * This should have been validated before. + */ + PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(offer))==PJ_SUCCESS, status); + + /* Create initial answer by duplicating initial SDP, + * but clear all media lines. The media lines will be filled up later. + */ + answer = pjmedia_sdp_session_clone(pool, initial); + PJ_ASSERT_RETURN(answer != NULL, PJ_ENOMEM); + + answer->media_count = 0; + + pj_memset(media_used, 0, sizeof(media_used)); + + /* For each media line, create our answer based on our initial + * capability. + */ + for (i=0; i<offer->media_count; ++i) { + const pjmedia_sdp_media *om; /* offer */ + const pjmedia_sdp_media *im; /* initial media */ + pjmedia_sdp_media *am = NULL; /* answer/result */ + unsigned j; + + om = offer->media[i]; + + /* Find media description in our initial capability that matches + * the media type and transport type of offer's media, has + * matching codec, and has not been used to answer other offer. + */ + for (im=NULL, j=0; j<initial->media_count; ++j) { + im = initial->media[j]; + if (pj_strcmp(&om->desc.media, &im->desc.media)==0 && + pj_strcmp(&om->desc.transport, &im->desc.transport)==0 && + media_used[j] == 0) + { + /* See if it has matching codec. */ + pj_bool_t match; + + match = match_offer(pool, om, im, &am); + if (match) { + /* Mark media as used. */ + media_used[j] = 1; + break; + } + } + } + + if (j==initial->media_count) { + /* No matching media. + * Reject the offer by setting the port to zero in the answer. + */ + /* For simplicity in the construction of the answer, we'll + * just clone the media from the offer. Anyway receiver will + * ignore anything in the media once it sees that the port + * number is zero. + */ + am = pjmedia_sdp_media_clone(pool, om); + am->desc.port = 0; + + /* Match direction */ + update_media_direction(pool, om, am); + + } else { + /* The answer is in am */ + pj_assert(am != NULL); + } + + /* Add the media answer */ + answer->media[answer->media_count++] = am; + } + + *p_answer = answer; + return PJ_SUCCESS; +} + +/* The best bit: SDP negotiation function! */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_negotiate( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + pj_bool_t allow_asym) +{ + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg, PJ_EINVAL); + + /* Must be in STATE_WAIT_NEGO state. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, + PJMEDIA_SDPNEG_EINSTATE); + + /* Must have remote offer. */ + PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJ_EBUG); + + if (neg->has_remote_answer) { + pjmedia_sdp_session *active; + status = process_answer(pool, neg->neg_local_sdp, neg->neg_remote_sdp, + allow_asym, &active); + if (status == PJ_SUCCESS) { + /* Only update active SDPs when negotiation is successfull */ + neg->active_local_sdp = active; + neg->active_remote_sdp = neg->neg_remote_sdp; + + } + } else { + pjmedia_sdp_session *answer; + + status = create_answer(pool, neg->initial_sdp, neg->neg_remote_sdp, + &answer); + if (status == PJ_SUCCESS) { + pj_uint32_t active_ver; + + if (neg->active_local_sdp) + active_ver = neg->active_local_sdp->origin.version; + else + active_ver = neg->initial_sdp->origin.version; + + /* Only update active SDPs when negotiation is successfull */ + neg->active_local_sdp = answer; + neg->active_remote_sdp = neg->neg_remote_sdp; + + /* Increment SDP version */ + neg->active_local_sdp->origin.version = ++active_ver; + } + } + + /* State is DONE regardless */ + neg->state = PJMEDIA_SDP_NEG_STATE_DONE; + + /* Clear temporary SDP */ + neg->neg_local_sdp = neg->neg_remote_sdp = NULL; + neg->has_remote_answer = 0; + + return status; +} + diff --git a/pjmedia/src/test/main.c b/pjmedia/src/test/main.c index 31ca3c32..31acaec7 100644 --- a/pjmedia/src/test/main.c +++ b/pjmedia/src/test/main.c @@ -16,27 +16,17 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <pj/os.h> -#include <pj/pool.h> -#include <pjmedia/sound.h> - -pj_status_t session_test (pj_pool_factory *pf); -pj_status_t rtp_test (pj_pool_factory *pf); -pj_status_t sdp_test(pj_pool_factory *pf); -int jbuf_main(pj_pool_factory *pf); +#include "test.h" int main() { - pj_caching_pool caching_pool; + int rc; + char s[10]; - pj_init(); - pj_caching_pool_init(&caching_pool, &pj_pool_factory_default_policy, 0); + rc = test_main(); - sdp_test (&caching_pool.factory); - rtp_test(&caching_pool.factory); - session_test (&caching_pool.factory); - //jbuf_main(&caching_pool.factory); + puts("\nPress <ENTER> to quit"); + fgets(s, sizeof(s), stdin); - pj_caching_pool_destroy(&caching_pool); - return 0; + return rc; } diff --git a/pjmedia/src/test/sdp_neg_test.c b/pjmedia/src/test/sdp_neg_test.c new file mode 100644 index 00000000..ff474a9a --- /dev/null +++ b/pjmedia/src/test/sdp_neg_test.c @@ -0,0 +1,1209 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms oa the GNU General Public License as published by + * the Free Software Foundation; either version 2 oa the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty oa + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy oa the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia/sdp.h> +#include <pjmedia/sdp_neg.h> +#include "test.h" + + +#define THIS_FILE "sdp_neg_test.c" +#define START_TEST 0 + +enum session_type +{ + REMOTE_OFFER, + LOCAL_OFFER, +}; + +struct offer_answer +{ + enum session_type type; /* LOCAL_OFFER: REMOTE_OFFER: */ + char *sdp1; /* local offer remote offer */ + char *sdp2; /* remote answer initial local */ + char *sdp3; /* local active media local answer */ +}; + +struct test +{ + const char *title; + unsigned offer_answer_count; + struct offer_answer offer_answer[4]; +} test[] = +{ + /* test 0: */ + { + /********************************************************************* + * RFC 3264 examples, section 10.1 (Alice's view) + * + * Difference from the example: + * - Bob's port number of the third media stream in the first answer + * is changed (make it different than Alice's) + * - in the second offer/answer exchange, Alice can't accept the + * additional line since she didn't specify the capability + * in the initial negotiator creation. + */ + + "RFC 3264 example 10.1 (Alice's view)", + 2, + { + { + LOCAL_OFFER, + /* Alice sends offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 51372 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* Received Bob's answer: */ + "v=0\r\n" + "o=bob 2890844730 2890844730 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "m=video 53002 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* Alice's SDP now: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + }, + { + REMOTE_OFFER, + /* Bob wants to change his local SDP + * (change local port for the first stream and add new stream) + * Received SDP from Bob: + */ + "v=0\r\n" + "o=bob 2890844730 2890844731 IN IP4 host.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 65422 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "m=video 53002 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + "m=audio 51434 RTP/AVP 110\r\n" + "a=rtpmap:110 telephone-events/8000\r\n" + "a=recvonly\r\n", + NULL, + /* Alice's SDP now */ + "v=0\r\n" + "o=alice 2890844526 2890844527 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" /* <-- this is not necessary */ + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + "m=audio 0 RTP/AVP 110\r\n" + "a=rtpmap:110 telephone-events/8000\r\n" + "a=sendonly\r\n" + } + } + }, + + /* test 1: */ + { + /********************************************************************* + * RFC 3264 examples, section 10.1. (Bob's view) + * + * Difference: + * - the SDP version in Bob's capability is changed to ver-1. + */ + + "RFC 3264 example 10.1 (Bob's view)", + 2, + { + { + REMOTE_OFFER, + /* Remote offer from Alice: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 51372 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* Bob's capability: */ + "v=0\r\n" + "o=bob 2890844730 2890844729 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* This's how Bob's answer should look like: */ + "v=0\r\n" + "o=bob 2890844730 2890844730 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + }, + { + LOCAL_OFFER, + /* Bob wants to change his local SDP + * (change local port for the first stream and add new stream) + */ + "v=0\r\n" + "o=bob 2890844730 2890844731 IN IP4 host.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 65422 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + "m=audio 51434 RTP/AVP 110\r\n" + "a=rtpmap:110 telephone-events/8000\r\n" + "a=recvonly\r\n", + /* Got answer from Alice */ + "v=0\r\n" + "o=alice 2890844526 2890844527 IN IP4 host.anywhere.com\r\n" + "s=-\r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + "m=audio 53122 RTP/AVP 110\r\n" + "a=rtpmap:110 telephone-events/8000\r\n" + "a=sendonly\r\n", + /* This is how Bob's SDP should look like after negotiation */ + "v=0\r\n" + "o=bob 2890844730 2890844731 IN IP4 host.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 65422 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + "m=audio 51434 RTP/AVP 110\r\n" + "a=rtpmap:110 telephone-events/8000\r\n" + "a=recvonly\r\n" + } + } + }, + + /* test 2: */ + { + /********************************************************************* + * RFC 3264 examples, section 10.2. + * This is from Alice's point of view. + */ + + "RFC 3264 example 10.2 (Alice's view)", + 2, + { + { + LOCAL_OFFER, + /* The initial offer from Alice to Bob indicates a single audio + * stream with the three audio codecs that are available in the + * DSP. The stream is marked as inactive, + */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n" + "s=-\r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 62986 RTP/AVP 0 4 18\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=rtpmap:18 G729/8000\r\n" + "a=inactive\r\n", + /* Bob can support dynamic switching between PCMU and G.723. So, + * he sends the following answer: + */ + "v=0\r\n" + "o=bob 2890844730 2890844731 IN IP4 host.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 54344 RTP/AVP 0 4\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=inactive\r\n", + /* This is how Alice's media should look like after negotiation */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n" + "s=-\r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 62986 RTP/AVP 0 4\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=inactive\r\n", + }, + { + LOCAL_OFFER, + /* Alice sends an updated offer with a sendrecv stream: */ + "v=0\r\n" + "o=alice 2890844526 2890844527 IN IP4 host.anywhere.com\r\n" + "s=-\r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 62986 RTP/AVP 4\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=sendrecv\r\n", + /* Bob accepts the single codec: */ + "v=0\r\n" + "o=bob 2890844730 2890844732 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 54344 RTP/AVP 4\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=sendrecv\r\n", + /* This is how Alice's media should look like after negotiation */ + "v=0\r\n" + "o=alice 2890844526 2890844527 IN IP4 host.anywhere.com\r\n" + "s=-\r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 62986 RTP/AVP 4\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=sendrecv\r\n" + } + } + }, + + /* test 3: */ + { + /********************************************************************* + * RFC 3264 examples, section 10.2. + * This is from Bob's point of view. + * + * Difference: + * - The SDP version number in Bob's initial capability is ver-1 + */ + + "RFC 3264 example 10.2 (Bob's view)", + 2, + { + { + REMOTE_OFFER, + /* Bob received offer from Alice: + */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n" + "s=-\r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 62986 RTP/AVP 0 4 18\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=rtpmap:18 G729/8000\r\n" + "a=inactive\r\n", + /* Bob's capability: + */ + "v=0\r\n" + "o=bob 2890844730 2890844730 IN IP4 host.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 54344 RTP/AVP 0 4\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=inactive\r\n", + /* This is how Bob's media should look like after negotiation */ + "v=0\r\n" + "o=bob 2890844730 2890844731 IN IP4 host.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 54344 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=inactive\r\n" + }, + { + REMOTE_OFFER, + /* Received updated Alice's SDP: offer with a sendrecv stream: */ + "v=0\r\n" + "o=alice 2890844526 2890844527 IN IP4 host.anywhere.com\r\n" + "s=-\r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 62986 RTP/AVP 4\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=sendrecv\r\n", + /* Bob accepts the single codec: */ + NULL, + /* This is how Bob's media should look like after negotiation */ + "v=0\r\n" + "o=bob 2890844730 2890844732 IN IP4 host.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 54344 RTP/AVP 4\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=sendrecv\r\n", + } + } + }, + + /* test 4: */ + { + /********************************************************************* + * RFC 4317 Sample 2.1: Audio and Video 1 (Alice's view) + * + * This common scenario shows a video and audio session in which + * multiple codecs are offered but only one is accepted. As a result of + * the exchange shown below, Alice and Bob may send only PCMU audio and + * MPV video. Note: Dynamic payload type 97 is used for iLBC codec + */ + "RFC 4317 section 2.1: Audio and Video 1 (Alice's view)", + 1, + { + { + LOCAL_OFFER, + /* Alice's local offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0 8 97\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=video 51372 RTP/AVP 31 32\r\n" + "a=rtpmap:31 H261/90000\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* Received answer from Bob: */ + "v=0\r\n" + "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49174 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 49170 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* This is how Alice's media should look like now: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 51372 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + } + } + }, + + /* test 5: */ + { + /********************************************************************* + * RFC 4317 Sample 2.1: Audio and Video 1 (Bob's view) + * + * This common scenario shows a video and audio session in which + * multiple codecs are offered but only one is accepted. As a result of + * the exchange shown below, Alice and Bob may send only PCMU audio and + * MPV video. Note: Dynamic payload type 97 is used for iLBC codec + * + * Difference: + * - Bob's initial capability version number + */ + "RFC 4317 section 2.1: Audio and Video 1 (Bob's view)", + 1, + { + { + REMOTE_OFFER, + /* Received Alice's local offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0 8 97\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=video 51372 RTP/AVP 31 32\r\n" + "a=rtpmap:31 H261/90000\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* Bob's capability: */ + "v=0\r\n" + "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49174 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 49170 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* This is how Bob's media should look like now: */ + "v=0\r\n" + "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49174 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 49170 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + } + } + }, + + /* test 6: */ + { + /********************************************************************* + * RFC 4317 Sample 2.2: Audio and Video 2 (Alice's view) + * + * Difference: + * - Bob's initial capability version number + */ + "RFC 4317 section 2.2: Audio and Video 2 (Alice's view)", + 2, + { + { + LOCAL_OFFER, + /* Alice sends offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0 8 97\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=video 51372 RTP/AVP 31 32\r\n" + "a=rtpmap:31 H261/90000\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* Bob's answer: */ + "v=0\r\n" + "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49172 RTP/AVP 0 8\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n", + /* This is how Alice's media should look like now: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0 8\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" + }, + { + LOCAL_OFFER, + /* Alice sends updated offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 51372 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n", + /* Bob's answer: */ + "v=0\r\n" + "o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49172 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n", + /* This is how Alice's SDP should look like: */ + "v=0\r\n" + "o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 51372 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" + } + } + }, + + /* test 7: */ + { + /********************************************************************* + * RFC 4317 Sample 2.2: Audio and Video 2 (Bob's view) + * + * Difference: + * - Bob's initial capability version number + */ + "RFC 4317 section 2.2: Audio and Video 2 (Bob's view)", + 2, + { + { + REMOTE_OFFER, + /* Received offer from alice: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0 8 97\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=video 51372 RTP/AVP 31 32\r\n" + "a=rtpmap:31 H261/90000\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* Bob's initial capability: */ + "v=0\r\n" + "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49172 RTP/AVP 0 8\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n", + /* This is how Bob's answer should look like now: */ + "v=0\r\n" + "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49172 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" + }, + { + REMOTE_OFFER, + /* Received updated offer from Alice: */ + "v=0\r\n" + "o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 51372 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n", + /* Bob's answer: */ + NULL, + /* This is how Bob's answer should look like: */ + "v=0\r\n" + "o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49172 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" + } + } + }, + + /* test 8: */ + { + /********************************************************************* + * RFC 4317 Sample 2.4: Audio and Telephone-Events (Alice's view) + * + */ + + "RFC 4317 section 2.4: Audio and Telephone-Events (Alice's view)", + 1, + { + { + LOCAL_OFFER, + /* Alice sends offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0 97\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=audio 49172 RTP/AVP 98\r\n" + "a=rtpmap:98 telephone-event/8000\r\n" + "a=sendonly\r\n", + /* Received Bob's answer: */ + "v=0\r\n" + "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49172 RTP/AVP 97\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=audio 49174 RTP/AVP 98\r\n" + "a=rtpmap:98 telephone-event/8000\r\n" + "a=recvonly\r\n", + /* Alice's SDP now: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 97\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=audio 49172 RTP/AVP 98\r\n" + "a=rtpmap:98 telephone-event/8000\r\n" + "a=sendonly\r\n" + } + } + }, + + + /* test 9: */ + { + /********************************************************************* + * RFC 4317 Sample 2.4: Audio and Telephone-Events (Bob's view) + * + * Difference: + * - Bob's initial SDP version number + * - Bob's capability are added with more formats, and the + * stream order is interchanged to test the negotiator. + */ + + "RFC 4317 section 2.4: Audio and Telephone-Events (Bob's view)", + 1, + { + { + REMOTE_OFFER, + /* Received Alice's offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0 97\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=audio 49172 RTP/AVP 98\r\n" + "a=rtpmap:98 telephone-event/8000\r\n" + "a=sendonly\r\n", + /* Bob's initial capability: */ + "v=0\r\n" + "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49174 RTP/AVP 4 98\r\n" + "a=rtpmap:98 telephone-event/8000\r\n" + "m=audio 49172 RTP/AVP 97 8 99\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "a=rtpmap:99 telephone-event/8000\r\n" + "a=recvonly\r\n", + /* Bob's answer should be: */ + "v=0\r\n" + "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49172 RTP/AVP 97\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=audio 49174 RTP/AVP 98\r\n" + "a=rtpmap:98 telephone-event/8000\r\n" + "a=recvonly\r\n" + } + } + }, + + /* test 10: */ + { + /********************************************************************* + * RFC 4317 Sample 2.6: Audio with Telephone-Events (Alice's view) + * + */ + + "RFC 4317 section 2.6: Audio with Telephone-Events (Alice's view)", + 1, + { + { + LOCAL_OFFER, + /* Alice sends offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=audio 51372 RTP/AVP 97 101\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "a=rtpmap:101 telephone-event/8000\r\n", + /* Received bob's answer: */ + "v=0\r\n" + "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 0 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=audio 49170 RTP/AVP 97 101\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "a=rtpmap:101 telephone-event/8000\r\n", + /* Alice's local SDP should be: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 0 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=audio 51372 RTP/AVP 97 101\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "a=rtpmap:101 telephone-event/8000\r\n" + } + } + }, + + /* test 11: */ + { + /********************************************************************* + * RFC 4317 Sample 2.6: Audio with Telephone-Events (Bob's view) + * + * Difference: + * - Bob's SDP version number + * - Bob's initial capability are expanded with multiple m lines + * and more formats + */ + + "RFC 4317 section 2.6: Audio with Telephone-Events (Bob's view)", + 1, + { + { + REMOTE_OFFER, + /* Received Alice's offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=audio 51372 RTP/AVP 97 101\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "a=rtpmap:101 telephone-event/8000\r\n", + /* Bob's initial capability also has video: */ + "v=0\r\n" + "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 4 97 101\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "a=rtpmap:101 telephone-event/8000\r\n" + "m=video 1000 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n", + /* Bob's answer should be: */ + "v=0\r\n" + "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 0 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=audio 49170 RTP/AVP 97 101\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "a=rtpmap:101 telephone-event/8000\r\n", + } + } + }, + +}; + +static const char *find_diff(const char *s1, const char *s2, + int *line) +{ + *line = 1; + while (*s2 && *s1) { + if (*s2 != *s1) + return s2; + if (*s2 == '\n') + ++*line; + ++s2, ++s1; + } + + return s2; +} + +static int compare_sdp_string(const char *cmp_title, + const char *title1, + const pjmedia_sdp_session *sdp1, + const char *title2, + const pjmedia_sdp_session *sdp2, + pj_status_t logical_cmp) +{ + char sdpbuf1[1024], sdpbuf2[1024]; + pj_ssize_t len1, len2; + + len1 = pjmedia_sdp_print(sdp1, sdpbuf1, sizeof(sdpbuf1)); + if (len1 < 1) { + PJ_LOG(3,(THIS_FILE," error: printing sdp1")); + return -1; + } + sdpbuf1[len1] = '\0'; + + len2 = pjmedia_sdp_print(sdp2, sdpbuf2, sizeof(sdpbuf2)); + if (len2 < 1) { + PJ_LOG(3,(THIS_FILE," error: printing sdp2")); + return -1; + } + sdpbuf2[len2] = '\0'; + + if (logical_cmp != PJ_SUCCESS) { + char errbuf[80]; + + pjmedia_strerror(logical_cmp, errbuf, sizeof(errbuf)); + + PJ_LOG(3,(THIS_FILE,"%s mismatch: %s\n" + "%s:\n" + "%s\n" + "%s:\n" + "%s\n", + cmp_title, + errbuf, + title1, sdpbuf1, + title2, sdpbuf2)); + + return -1; + + } else if (strcmp(sdpbuf1, sdpbuf2) != 0) { + int line; + const char *diff; + + PJ_LOG(3,(THIS_FILE,"%s mismatch:\n" + "%s:\n" + "%s\n" + "%s:\n" + "%s\n", + cmp_title, + title1, sdpbuf1, + title2, sdpbuf2)); + + diff = find_diff(sdpbuf1, sdpbuf2, &line); + PJ_LOG(3,(THIS_FILE,"Difference: line %d:\n" + "%s", + line, diff)); + return -1; + + } else { + return 0; + } + +} + +static int offer_answer_test(pj_pool_t *pool, pjmedia_sdp_neg **p_neg, + struct offer_answer *oa) +{ + pjmedia_sdp_session *sdp1; + pjmedia_sdp_neg *neg; + pj_status_t status; + + status = pjmedia_sdp_parse(pool, oa->sdp1, pj_native_strlen(oa->sdp1), + &sdp1); + if (status != PJ_SUCCESS) { + app_perror(status, " error: unexpected parse status for sdp1"); + return -10; + } + + status = pjmedia_sdp_validate(sdp1); + if (status != PJ_SUCCESS) { + app_perror(status, " error: sdp1 validation failed"); + return -15; + } + + neg = *p_neg; + + if (oa->type == LOCAL_OFFER) { + + /* + * Local creates offer first. + */ + pjmedia_sdp_session *sdp2, *sdp3; + const pjmedia_sdp_session *active; + + if (neg == NULL) { + /* Create negotiator with local offer. */ + status = pjmedia_sdp_neg_create_w_local_offer(pool, sdp1, &neg); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_create_w_local_offer"); + return -20; + } + *p_neg = neg; + + } else { + /* Modify local offer */ + status = pjmedia_sdp_neg_modify_local_offer(pool, neg, sdp1); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_modify_local_offer"); + return -30; + } + } + + /* Parse and validate remote answer */ + status = pjmedia_sdp_parse(pool, oa->sdp2, pj_native_strlen(oa->sdp2), + &sdp2); + if (status != PJ_SUCCESS) { + app_perror(status, " error: parsing sdp2"); + return -40; + } + + status = pjmedia_sdp_validate(sdp2); + if (status != PJ_SUCCESS) { + app_perror(status, " error: sdp2 validation failed"); + return -50; + } + + /* Give the answer to negotiator. */ + status = pjmedia_sdp_neg_rx_remote_answer(pool, neg, sdp2); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_rx_remote_answer"); + return -60; + } + + /* Negotiate remote answer with local answer */ + status = pjmedia_sdp_neg_negotiate(pool, neg, 0); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_negotiate"); + return -70; + } + + /* Get the local active media. */ + status = pjmedia_sdp_neg_get_local(neg, &active); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_get_local"); + return -80; + } + + /* Parse and validate the correct active media. */ + status = pjmedia_sdp_parse(pool, oa->sdp3, pj_native_strlen(oa->sdp3), + &sdp3); + if (status != PJ_SUCCESS) { + app_perror(status, " error: parsing sdp3"); + return -90; + } + + status = pjmedia_sdp_validate(sdp3); + if (status != PJ_SUCCESS) { + app_perror(status, " error: sdp3 validation failed"); + return -100; + } + + /* Compare active with sdp3 */ + status = pjmedia_sdp_session_cmp(active, sdp3, 0); + if (status != PJ_SUCCESS) { + app_perror(status, " error: active local comparison mismatch"); + compare_sdp_string("Logical cmp after negotiatin remote answer", + "Active local sdp from negotiator", active, + "The correct active local sdp", sdp3, + status); + return -110; + } + + /* Compare the string representation oa both sdps */ + status = compare_sdp_string("String cmp after negotiatin remote answer", + "Active local sdp from negotiator", active, + "The correct active local sdp", sdp3, + PJ_SUCCESS); + if (status != 0) + return -120; + + } else { + /* + * Remote creates offer first. + */ + + pjmedia_sdp_session *sdp2, *sdp3; + const pjmedia_sdp_session *answer; + + if (neg == NULL) { + /* Parse and validate initial local capability */ + status = pjmedia_sdp_parse(pool, oa->sdp2, pj_native_strlen(oa->sdp2), + &sdp2); + if (status != PJ_SUCCESS) { + app_perror(status, " error: parsing sdp2"); + return -200; + } + + status = pjmedia_sdp_validate(sdp2); + if (status != PJ_SUCCESS) { + app_perror(status, " error: sdp2 validation failed"); + return -210; + } + + /* Create negotiator with remote offer. */ + status = pjmedia_sdp_neg_create_w_remote_offer(pool, sdp2, sdp1, &neg); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_create_w_remote_offer"); + return -220; + } + *p_neg = neg; + + } else { + /* Received subsequent offer from remote. */ + status = pjmedia_sdp_neg_rx_remote_offer(pool, neg, sdp1); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_rx_remote_offer"); + return -230; + } + } + + /* Negotiate. */ + status = pjmedia_sdp_neg_negotiate(pool, neg, 0); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_negotiate"); + return -240; + } + + /* Get our answer. */ + status = pjmedia_sdp_neg_get_local(neg, &answer); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_get_local"); + return -250; + } + + /* Parse the correct answer. */ + status = pjmedia_sdp_parse(pool, oa->sdp3, pj_native_strlen(oa->sdp3), + &sdp3); + if (status != PJ_SUCCESS) { + app_perror(status, " error: parsing sdp3"); + return -260; + } + + /* Validate the correct answer. */ + status = pjmedia_sdp_validate(sdp3); + if (status != PJ_SUCCESS) { + app_perror(status, " error: sdp3 validation failed"); + return -270; + } + + /* Compare answer from negotiator and the correct answer */ + status = pjmedia_sdp_session_cmp(sdp3, answer, 0); + if (status != PJ_SUCCESS) { + compare_sdp_string("Logical cmp after negotiating remote offer", + "Local answer from negotiator", answer, + "The correct local answer", sdp3, + status); + + return -280; + } + + /* Compare the string representation oa both answers */ + status = compare_sdp_string("String cmp after negotiating remote offer", + "Local answer from negotiator", answer, + "The correct local answer", sdp3, + PJ_SUCCESS); + if (status != 0) + return -290; + + } + + return 0; +} + +static int perform_test(pj_pool_t *pool, int test_index) +{ + pjmedia_sdp_neg *neg = NULL; + unsigned i; + int rc; + + for (i=0; i<test[test_index].offer_answer_count; ++i) { + + rc = offer_answer_test(pool, &neg, &test[test_index].offer_answer[i]); + if (rc != 0) { + PJ_LOG(3,(THIS_FILE, " test %d offer answer %d failed with status %d", + test_index, i, rc)); + return rc; + } + } + + return 0; +} + +int sdp_neg_test() +{ + unsigned i; + int status; + + for (i=START_TEST; i<PJ_ARRAY_SIZE(test); ++i) { + pj_pool_t *pool; + + pool = pj_pool_create(mem, "sdp_neg_test", 4000, 4000, NULL); + if (!pool) + return PJ_ENOMEM; + + PJ_LOG(3,(THIS_FILE," test %d: %s", i, test[i].title)); + status = perform_test(pool, i); + + pj_pool_release(pool); + + if (status != 0) { + return status; + } + } + + return 0; +} + diff --git a/pjmedia/src/test/test.c b/pjmedia/src/test/test.c new file mode 100644 index 00000000..ff183a0e --- /dev/null +++ b/pjmedia/src/test/test.c @@ -0,0 +1,75 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" + +#define THIS_FILE "test.c" + +#define DO_TEST(test) do { \ + PJ_LOG(3, (THIS_FILE, "Running %s...", #test)); \ + rc = test; \ + PJ_LOG(3, (THIS_FILE, \ + "%s(%d)", \ + (rc ? "..ERROR" : "..success"), rc)); \ + if (rc!=0) goto on_return; \ + } while (0) + + +pj_pool_factory *mem; + + +void app_perror(pj_status_t status, const char *msg) +{ + char errbuf[PJMEDIA_ERR_MSG_SIZE]; + + pjmedia_strerror(status, errbuf, sizeof(errbuf)); + + PJ_LOG(3,(THIS_FILE, "%s: %s", msg, errbuf)); +} + +int test_main(void) +{ + int rc; + pj_caching_pool caching_pool; + + pj_init(); + pj_caching_pool_init(&caching_pool, &pj_pool_factory_default_policy, 0); + + pj_log_set_decor(PJ_LOG_HAS_NEWLINE); + + mem = &caching_pool.factory; + + DO_TEST(sdp_neg_test()); + //sdp_test (&caching_pool.factory); + //rtp_test(&caching_pool.factory); + //session_test (&caching_pool.factory); + //jbuf_main(&caching_pool.factory); + + PJ_LOG(3,(THIS_FILE," ")); + +on_return: + + if (rc != 0) { + PJ_LOG(3,(THIS_FILE,"Test completed with error(s)!")); + } else { + PJ_LOG(3,(THIS_FILE,"Looks like everything is okay!")); + } + + pj_caching_pool_destroy(&caching_pool); + return rc; +} diff --git a/pjmedia/src/test/test.h b/pjmedia/src/test/test.h new file mode 100644 index 00000000..6f5276e6 --- /dev/null +++ b/pjmedia/src/test/test.h @@ -0,0 +1,37 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_TEST_H__ +#define __PJMEDIA_TEST_H__ + +#include <pjmedia.h> +#include <pjlib.h> + + +int session_test(void); +int rtp_test(void); +int sdp_test(void); +int jbuf_main(void); +int sdp_neg_test(void); + +extern pj_pool_factory *mem; +void app_perror(pj_status_t status, const char *title); + +int test_main(void); + +#endif /* __PJMEDIA_TEST_H__ */ |