summaryrefslogtreecommitdiff
path: root/pjmedia/src
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2006-02-02 19:16:07 +0000
committerBenny Prijono <bennylp@teluu.com>2006-02-02 19:16:07 +0000
commit5125fc4191998ab13529ee59706f95116fc1725a (patch)
treee1f9b4fe1c3c3a1a734e099333c7107678065df9 /pjmedia/src
parent6cbba81e8937d6793705608e7e68e83ec950dd2a (diff)
Added SDP negotiator and changed SDP structs (tested)
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@129 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjmedia/src')
-rw-r--r--pjmedia/src/pjmedia/errno.c139
-rw-r--r--pjmedia/src/pjmedia/jbuf.c2
-rw-r--r--pjmedia/src/pjmedia/sdp.c998
-rw-r--r--pjmedia/src/pjmedia/sdp_cmp.c292
-rw-r--r--pjmedia/src/pjmedia/sdp_neg.c815
-rw-r--r--pjmedia/src/test/main.c24
-rw-r--r--pjmedia/src/test/sdp_neg_test.c1209
-rw-r--r--pjmedia/src/test/test.c75
-rw-r--r--pjmedia/src/test/test.h37
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__ */