diff options
Diffstat (limited to 'pjmedia/src/pjmedia/sdp.c')
-rw-r--r-- | pjmedia/src/pjmedia/sdp.c | 998 |
1 files changed, 606 insertions, 392 deletions
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; +} + + |