blob: 5451ed35d64ef9e39f8447f0117b1c8ccd9aff8c [file] [log] [blame]
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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 <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,
SYNTAX_ERROR = 1,
};
// New token definition from RFC 4566 (SDP)
#define TOKEN "!#$%&'*+-.^_`{|}~"
//#define TOKEN "-.!%*_=`'~"
//#define TOKEN "'`-./:?\"#$&*;=@[]^_`{|}+~!"
#define NTP_OFFSET ((pj_uint32_t)2208988800)
#define THIS_FILE "sdp.c"
typedef struct parse_context
{
pj_status_t last_error;
} parse_context;
/*
* Prototypes for line parser.
*/
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 void parse_bandwidth_info(pj_scanner *scanner, pjmedia_sdp_bandw *bandw,
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);
static void on_scanner_error(pj_scanner *scanner);
/*
* Scanner character specification.
*/
static int is_initialized;
static pj_cis_buf_t cis_buf;
static pj_cis_t cs_digit, cs_token;
static void init_sdp_parser(void)
{
if (is_initialized != 0)
return;
pj_enter_critical_section();
if (is_initialized != 0) {
pj_leave_critical_section();
return;
}
pj_cis_buf_init(&cis_buf);
pj_cis_init(&cis_buf, &cs_token);
pj_cis_add_alpha(&cs_token);
pj_cis_add_num(&cs_token);
pj_cis_add_str(&cs_token, TOKEN);
pj_cis_init(&cis_buf, &cs_digit);
pj_cis_add_num(&cs_digit);
is_initialized = 1;
pj_leave_critical_section();
}
PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_create( pj_pool_t *pool,
const char *name,
const pj_str_t *value)
{
pjmedia_sdp_attr *attr;
PJ_ASSERT_RETURN(pool && name, NULL);
attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr);
pj_strdup2(pool, &attr->name, name);
if (value)
pj_strdup_with_null(pool, &attr->value, value);
else {
attr->value.ptr = NULL;
attr->value.slen = 0;
}
return attr;
}
PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_clone(pj_pool_t *pool,
const pjmedia_sdp_attr *rhs)
{
pjmedia_sdp_attr *attr;
PJ_ASSERT_RETURN(pool && rhs, NULL);
attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr);
pj_strdup(pool, &attr->name, &rhs->name);
pj_strdup_with_null(pool, &attr->value, &rhs->value);
return attr;
}
PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_find (unsigned count,
pjmedia_sdp_attr *const attr_array[],
const pj_str_t *name,
const pj_str_t *c_fmt)
{
unsigned i;
unsigned c_pt = 0xFFFF;
if (c_fmt)
c_pt = pj_strtoul(c_fmt);
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) {
unsigned pt = (unsigned) pj_strtoul2(&a->value, NULL, 10);
if (pt == c_pt) {
return (pjmedia_sdp_attr*)a;
}
} else
return (pjmedia_sdp_attr*)a;
}
}
return NULL;
}
PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_find2(unsigned count,
pjmedia_sdp_attr *const attr_array[],
const char *c_name,
const pj_str_t *c_fmt)
{
pj_str_t name;
name.ptr = (char*)c_name;
name.slen = pj_ansi_strlen(c_name);
return pjmedia_sdp_attr_find(count, attr_array, &name, c_fmt);
}
PJ_DEF(pj_status_t) pjmedia_sdp_attr_add(unsigned *count,
pjmedia_sdp_attr *attr_array[],
pjmedia_sdp_attr *attr)
{
PJ_ASSERT_RETURN(count && attr_array && attr, PJ_EINVAL);
PJ_ASSERT_RETURN(*count < PJMEDIA_MAX_SDP_ATTR, PJ_ETOOMANY);
attr_array[*count] = attr;
(*count)++;
return PJ_SUCCESS;
}
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_ansi_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;
}
PJ_DEF(pj_status_t) pjmedia_sdp_attr_remove( unsigned *count,
pjmedia_sdp_attr *attr_array[],
pjmedia_sdp_attr *attr )
{
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;
}
}
return removed ? PJ_SUCCESS : PJ_ENOTFOUND;
}
PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_rtpmap( const pjmedia_sdp_attr *attr,
pjmedia_sdp_rtpmap *rtpmap)
{
pj_scanner scanner;
pj_str_t token;
pj_status_t status = -1;
char term = 0;
PJ_USE_EXCEPTION;
PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "rtpmap")==0, PJ_EINVALIDOP);
PJ_ASSERT_RETURN(attr->value.slen != 0, PJMEDIA_SDP_EINATTR);
init_sdp_parser();
/* Check if input is null terminated, and null terminate if
* necessary. Unfortunately this may crash the application if
* attribute was allocated from a read-only memory location.
* But this shouldn't happen as attribute's value normally is
* null terminated.
*/
if (attr->value.ptr[attr->value.slen] != 0 &&
attr->value.ptr[attr->value.slen] != '\r' &&
attr->value.ptr[attr->value.slen] != '\n')
{
pj_assert(!"Shouldn't happen");
term = attr->value.ptr[attr->value.slen];
attr->value.ptr[attr->value.slen] = '\0';
}
pj_scan_init(&scanner, (char*)attr->value.ptr, attr->value.slen,
PJ_SCAN_AUTOSKIP_WS, &on_scanner_error);
/* rtpmap sample:
* a=rtpmap:98 L16/16000/2.
*/
/* Init */
rtpmap->pt.slen = rtpmap->param.slen = rtpmap->enc_name.slen = 0;
rtpmap->clock_rate = 0;
/* Parse */
PJ_TRY {
/* Get payload type. */
pj_scan_get(&scanner, &cs_token, &rtpmap->pt);
/* Get encoding name. */
pj_scan_get(&scanner, &cs_token, &rtpmap->enc_name);
/* Expecting '/' after encoding name. */
if (pj_scan_get_char(&scanner) != '/') {
status = PJMEDIA_SDP_EINRTPMAP;
goto on_return;
}
/* Get the clock rate. */
pj_scan_get(&scanner, &cs_digit, &token);
rtpmap->clock_rate = pj_strtoul(&token);
/* Expecting either '/' or EOF */
if (*scanner.curptr == '/') {
pj_scan_get_char(&scanner);
rtpmap->param.ptr = scanner.curptr;
rtpmap->param.slen = scanner.end - scanner.curptr;
} else {
rtpmap->param.slen = 0;
}
status = PJ_SUCCESS;
}
PJ_CATCH_ANY {
status = PJMEDIA_SDP_EINRTPMAP;
}
PJ_END;
on_return:
pj_scan_fini(&scanner);
if (term) {
attr->value.ptr[attr->value.slen] = term;
}
return status;
}
PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_fmtp( const pjmedia_sdp_attr *attr,
pjmedia_sdp_fmtp *fmtp)
{
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, "fmtp")==0, PJ_EINVALIDOP);
/* fmtp BNF:
* a=fmtp:<format> <format specific parameter>
*/
/* 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;
fmtp->fmt = token;
/* Expecting space after format. */
if (*p != ' ') return PJMEDIA_SDP_EINFMTP;
/* Get space. */
++p;
/* Set the remaining string as fmtp format parameter. */
fmtp->fmt_param.ptr = (char*)p;
fmtp->fmt_param.slen = end - p;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_rtcp(const pjmedia_sdp_attr *attr,
pjmedia_sdp_rtcp_attr *rtcp)
{
pj_scanner scanner;
pj_str_t token;
pj_status_t status = -1;
PJ_USE_EXCEPTION;
PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "rtcp")==0, PJ_EINVALIDOP);
init_sdp_parser();
/* fmtp BNF:
* a=rtcp:<port> [nettype addrtype address]
*/
pj_scan_init(&scanner, (char*)attr->value.ptr, attr->value.slen,
PJ_SCAN_AUTOSKIP_WS, &on_scanner_error);
/* Init */
rtcp->net_type.slen = rtcp->addr_type.slen = rtcp->addr.slen = 0;
/* Parse */
PJ_TRY {
/* Get the port */
pj_scan_get(&scanner, &cs_token, &token);
rtcp->port = pj_strtoul(&token);
/* Have address? */
if (!pj_scan_is_eof(&scanner)) {
/* Get network type */
pj_scan_get(&scanner, &cs_token, &rtcp->net_type);
/* Get address type */
pj_scan_get(&scanner, &cs_token, &rtcp->addr_type);
/* Get the address */
pj_scan_get(&scanner, &cs_token, &rtcp->addr);
}
status = PJ_SUCCESS;
}
PJ_CATCH_ANY {
status = PJMEDIA_SDP_EINRTCP;
}
PJ_END;
pj_scan_fini(&scanner);
return status;
}
PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_create_rtcp(pj_pool_t *pool,
const pj_sockaddr *a)
{
enum {
ATTR_LEN = PJ_INET6_ADDRSTRLEN+16
};
pjmedia_sdp_attr *attr;
attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr);
attr->name = pj_str("rtcp");
attr->value.ptr = (char*) pj_pool_alloc(pool, ATTR_LEN);
if (a->addr.sa_family == pj_AF_INET()) {
attr->value.slen =
pj_ansi_snprintf(attr->value.ptr, ATTR_LEN,
"%u IN IP4 %s",
pj_ntohs(a->ipv4.sin_port),
pj_inet_ntoa(a->ipv4.sin_addr));
} else if (a->addr.sa_family == pj_AF_INET6()) {
char tmp_addr[PJ_INET6_ADDRSTRLEN];
attr->value.slen =
pj_ansi_snprintf(attr->value.ptr, ATTR_LEN,
"%u IN IP6 %s",
pj_sockaddr_get_port(a),
pj_sockaddr_print(a, tmp_addr,
sizeof(tmp_addr), 0));
} else {
pj_assert(!"Unsupported address family");
return NULL;
}
return attr;
}
PJ_DEF(pj_status_t) pjmedia_sdp_attr_to_rtpmap(pj_pool_t *pool,
const pjmedia_sdp_attr *attr,
pjmedia_sdp_rtpmap **p_rtpmap)
{
PJ_ASSERT_RETURN(pool && attr && p_rtpmap, PJ_EINVAL);
*p_rtpmap = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_rtpmap);
PJ_ASSERT_RETURN(*p_rtpmap, PJ_ENOMEM);
return pjmedia_sdp_attr_get_rtpmap(attr, *p_rtpmap);
}
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[128];
int len;
/* 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);
attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr);
PJ_ASSERT_RETURN(attr != NULL, PJ_ENOMEM);
attr->name.ptr = "rtpmap";
attr->name.slen = 6;
/* Format: ":pt enc_name/clock_rate[/param]" */
len = pj_ansi_snprintf(tempbuf, sizeof(tempbuf),
"%.*s %.*s/%u%s%.*s",
(int)rtpmap->pt.slen,
rtpmap->pt.ptr,
(int)rtpmap->enc_name.slen,
rtpmap->enc_name.ptr,
rtpmap->clock_rate,
(rtpmap->param.slen ? "/" : ""),
(int)rtpmap->param.slen,
rtpmap->param.ptr);
if (len < 1 || len >= (int)sizeof(tempbuf))
return PJMEDIA_SDP_ERTPMAPTOOLONG;
attr->value.slen = len;
attr->value.ptr = (char*) pj_pool_alloc(pool, attr->value.slen+1);
pj_memcpy(attr->value.ptr, tempbuf, attr->value.slen+1);
*p_attr = attr;
return PJ_SUCCESS;
}
static int print_connection_info( pjmedia_sdp_conn *c, char *buf, int len)
{
int printed;
printed = pj_ansi_snprintf(buf, len, "c=%.*s %.*s %.*s\r\n",
(int)c->net_type.slen,
c->net_type.ptr,
(int)c->addr_type.slen,
c->addr_type.ptr,
(int)c->addr.slen,
c->addr.ptr);
if (printed < 1 || printed >= len)
return -1;
return printed;
}
PJ_DEF(pjmedia_sdp_conn*) pjmedia_sdp_conn_clone (pj_pool_t *pool,
const pjmedia_sdp_conn *rhs)
{
pjmedia_sdp_conn *c = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_conn);
if (!c) return NULL;
if (!pj_strdup (pool, &c->net_type, &rhs->net_type)) return NULL;
if (!pj_strdup (pool, &c->addr_type, &rhs->addr_type)) return NULL;
if (!pj_strdup (pool, &c->addr, &rhs->addr)) return NULL;
return c;
}
PJ_DEF(pjmedia_sdp_bandw*)
pjmedia_sdp_bandw_clone (pj_pool_t *pool,
const pjmedia_sdp_bandw *rhs)
{
pjmedia_sdp_bandw *b = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_bandw);
if (!b) return NULL;
if (!pj_strdup (pool, &b->modifier, &rhs->modifier)) return NULL;
b->value = rhs->value;
return b;
}
static pj_ssize_t print_bandw(const pjmedia_sdp_bandw *bandw,
char *buf, pj_size_t len)
{
char *p = buf;
if ((int)len < bandw->modifier.slen + 10 + 5)
return -1;
*p++ = 'b';
*p++ = '=';
pj_memcpy(p, bandw->modifier.ptr, bandw->modifier.slen);
p += bandw->modifier.slen;
*p++ = ':';
p += pj_utoa(bandw->value, p);
*p++ = '\r';
*p++ = '\n';
return p-buf;
}
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) {
*p++ = ':';
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;
unsigned i;
int printed;
/* check length for the "m=" line. */
if (len < m->desc.media.slen+m->desc.transport.slen+12+24) {
return -1;
}
*p++ = 'm'; /* m= */
*p++ = '=';
pj_memcpy(p, m->desc.media.ptr, m->desc.media.slen);
p += m->desc.media.slen;
*p++ = ' ';
printed = pj_utoa(m->desc.port, p);
p += printed;
if (m->desc.port_count > 1) {
*p++ = '/';
printed = pj_utoa(m->desc.port_count, p);
p += printed;
}
*p++ = ' ';
pj_memcpy(p, m->desc.transport.ptr, m->desc.transport.slen);
p += m->desc.transport.slen;
for (i=0; i<m->desc.fmt_count; ++i) {
*p++ = ' ';
pj_memcpy(p, m->desc.fmt[i].ptr, m->desc.fmt[i].slen);
p += m->desc.fmt[i].slen;
}
*p++ = '\r';
*p++ = '\n';
/* print connection info, if present. */
if (m->conn) {
printed = print_connection_info(m->conn, p, (int)(end-p));
if (printed < 0) {
return -1;
}
p += printed;
}
/* print optional bandwidth info. */
for (i=0; i<m->bandw_count; ++i) {
printed = (int)print_bandw(m->bandw[i], p, end-p);
if (printed < 0) {
return -1;
}
p += printed;
}
/* print attributes. */
for (i=0; i<m->attr_count; ++i) {
printed = (int)print_attr(m->attr[i], p, end-p);
if (printed < 0) {
return -1;
}
p += printed;
}
return (int)(p-buf);
}
PJ_DEF(pjmedia_sdp_media*) pjmedia_sdp_media_clone(
pj_pool_t *pool,
const pjmedia_sdp_media *rhs)
{
unsigned int i;
pjmedia_sdp_media *m = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_media);
PJ_ASSERT_RETURN(m != NULL, NULL);
pj_strdup (pool, &m->desc.media, &rhs->desc.media);
m->desc.port = rhs->desc.port;
m->desc.port_count = rhs->desc.port_count;
pj_strdup (pool, &m->desc.transport, &rhs->desc.transport);
m->desc.fmt_count = rhs->desc.fmt_count;
for (i=0; i<rhs->desc.fmt_count; ++i)
pj_strdup(pool, &m->desc.fmt[i], &rhs->desc.fmt[i]);
if (rhs->conn) {
m->conn = pjmedia_sdp_conn_clone (pool, rhs->conn);
PJ_ASSERT_RETURN(m->conn != NULL, NULL);
} else {
m->conn = NULL;
}
m->bandw_count = rhs->bandw_count;
for (i=0; i < rhs->bandw_count; ++i) {
m->bandw[i] = pjmedia_sdp_bandw_clone (pool, rhs->bandw[i]);
PJ_ASSERT_RETURN(m->bandw[i] != NULL, NULL);
}
m->attr_count = rhs->attr_count;
for (i=0; i < rhs->attr_count; ++i) {
m->attr[i] = pjmedia_sdp_attr_clone (pool, rhs->attr[i]);
PJ_ASSERT_RETURN(m->attr[i] != NULL, NULL);
}
return m;
}
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)
{
PJ_ASSERT_RETURN(m && name, NULL);
return pjmedia_sdp_attr_find(m->attr_count, m->attr, name, fmt);
}
PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_media_find_attr2(
const pjmedia_sdp_media *m,
const char *name, const pj_str_t *fmt)
{
PJ_ASSERT_RETURN(m && name, NULL);
return pjmedia_sdp_attr_find2(m->attr_count, m->attr, name, fmt);
}
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(pj_status_t) pjmedia_sdp_session_add_attr(pjmedia_sdp_session *s,
pjmedia_sdp_attr *attr)
{
return pjmedia_sdp_attr_add(&s->attr_count, s->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;
unsigned i;
int printed;
/* Check length for v= and o= lines. */
if (len < 5+
2+ses->origin.user.slen+18+
ses->origin.net_type.slen+ses->origin.addr.slen + 2)
{
return -1;
}
/* SDP version (v= line) */
pj_memcpy(p, "v=0\r\n", 5);
p += 5;
/* Owner (o=) line. */
*p++ = 'o';
*p++ = '=';
pj_memcpy(p, ses->origin.user.ptr, ses->origin.user.slen);
p += ses->origin.user.slen;
*p++ = ' ';
printed = pj_utoa(ses->origin.id, p);
p += printed;
*p++ = ' ';
printed = pj_utoa(ses->origin.version, p);
p += printed;
*p++ = ' ';
pj_memcpy(p, ses->origin.net_type.ptr, ses->origin.net_type.slen);
p += ses->origin.net_type.slen;
*p++ = ' ';
pj_memcpy(p, ses->origin.addr_type.ptr, ses->origin.addr_type.slen);
p += ses->origin.addr_type.slen;
*p++ = ' ';
pj_memcpy(p, ses->origin.addr.ptr, ses->origin.addr.slen);
p += ses->origin.addr.slen;
*p++ = '\r';
*p++ = '\n';
/* Session name (s=) line. */
if ((end-p) < 8+ses->name.slen) {
return -1;
}
*p++ = 's';
*p++ = '=';
pj_memcpy(p, ses->name.ptr, ses->name.slen);
p += ses->name.slen;
*p++ = '\r';
*p++ = '\n';
/* Connection line (c=) if exist. */
if (ses->conn) {
printed = print_connection_info(ses->conn, p, (int)(end-p));
if (printed < 1) {
return -1;
}
p += printed;
}
/* print optional bandwidth info. */
for (i=0; i<ses->bandw_count; ++i) {
printed = (int)print_bandw(ses->bandw[i], p, end-p);
if (printed < 1) {
return -1;
}
p += printed;
}
/* Time */
if ((end-p) < 24) {
return -1;
}
*p++ = 't';
*p++ = '=';
printed = pj_utoa(ses->time.start, p);
p += printed;
*p++ = ' ';
printed = pj_utoa(ses->time.stop, p);
p += printed;
*p++ = '\r';
*p++ = '\n';
/* Print all attribute (a=) lines. */
for (i=0; i<ses->attr_count; ++i) {
printed = (int)print_attr(ses->attr[i], p, end-p);
if (printed < 0) {
return -1;
}
p += printed;
}
/* Print media (m=) lines. */
for (i=0; i<ses->media_count; ++i) {
printed = print_media_desc(ses->media[i], p, (int)(end-p));
if (printed < 0) {
return -1;
}
p += printed;
}
return (int)(p-buf);
}
/******************************************************************************
* PARSERS
*/
static void parse_version(pj_scanner *scanner, parse_context *ctx)
{
ctx->last_error = PJMEDIA_SDP_EINVER;
/* check equal sign */
if (*(scanner->curptr+1) != '=') {
on_scanner_error(scanner);
return;
}
/* check version is 0 */
if (*(scanner->curptr+2) != '0') {
on_scanner_error(scanner);
return;
}
/* We've got what we're looking for, skip anything until newline */
pj_scan_skip_line(scanner);
}
static void parse_origin(pj_scanner *scanner, pjmedia_sdp_session *ses,
parse_context *ctx)
{
pj_str_t str;
ctx->last_error = PJMEDIA_SDP_EINORIGIN;
/* check equal sign */
if (*(scanner->curptr+1) != '=') {
on_scanner_error(scanner);
return;
}
/* o= */
pj_scan_advance_n(scanner, 2, SKIP_WS);
/* username. */
pj_scan_get_until_ch(scanner, ' ', &ses->origin.user);
pj_scan_get_char(scanner);
/* id */
pj_scan_get_until_ch(scanner, ' ', &str);
ses->origin.id = pj_strtoul(&str);
pj_scan_get_char(scanner);
/* version */
pj_scan_get_until_ch(scanner, ' ', &str);
ses->origin.version = pj_strtoul(&str);
pj_scan_get_char(scanner);
/* network-type */
pj_scan_get_until_ch(scanner, ' ', &ses->origin.net_type);
pj_scan_get_char(scanner);
/* addr-type */
pj_scan_get_until_ch(scanner, ' ', &ses->origin.addr_type);
pj_scan_get_char(scanner);
/* address */
pj_scan_get_until_chr(scanner, " \t\r\n", &ses->origin.addr);
/* We've got what we're looking for, skip anything until newline */
pj_scan_skip_line(scanner);
}
static void parse_time(pj_scanner *scanner, pjmedia_sdp_session *ses,
parse_context *ctx)
{
pj_str_t str;
ctx->last_error = PJMEDIA_SDP_EINTIME;
/* check equal sign */
if (*(scanner->curptr+1) != '=') {
on_scanner_error(scanner);
return;
}
/* t= */
pj_scan_advance_n(scanner, 2, SKIP_WS);
/* start time */
pj_scan_get_until_ch(scanner, ' ', &str);
ses->time.start = pj_strtoul(&str);
pj_scan_get_char(scanner);
/* stop time */
pj_scan_get_until_chr(scanner, " \t\r\n", &str);
ses->time.stop = pj_strtoul(&str);
/* We've got what we're looking for, skip anything until newline */
pj_scan_skip_line(scanner);
}
static void parse_generic_line(pj_scanner *scanner, pj_str_t *str,
parse_context *ctx)
{
ctx->last_error = PJMEDIA_SDP_EINSDP;
/* check equal sign */
if (*(scanner->curptr+1) != '=') {
on_scanner_error(scanner);
return;
}
/* x= */
pj_scan_advance_n(scanner, 2, SKIP_WS);
/* get anything until newline (including whitespaces). */
pj_scan_get_until_chr(scanner, "\r\n", str);
/* newline. */
pj_scan_get_newline(scanner);
}
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);
/* network-type */
pj_scan_get_until_ch(scanner, ' ', &conn->net_type);
pj_scan_get_char(scanner);
/* addr-type */
pj_scan_get_until_ch(scanner, ' ', &conn->addr_type);
pj_scan_get_char(scanner);
/* address. */
pj_scan_get_until_chr(scanner, "/ \t\r\n", &conn->addr);
PJ_TODO(PARSE_SDP_CONN_ADDRESS_SUBFIELDS);
/* We've got what we're looking for, skip anything until newline */
pj_scan_skip_line(scanner);
}
static void parse_bandwidth_info(pj_scanner *scanner, pjmedia_sdp_bandw *bandw,
parse_context *ctx)
{
pj_str_t str;
ctx->last_error = PJMEDIA_SDP_EINBANDW;
/* b= */
pj_scan_advance_n(scanner, 2, SKIP_WS);
/* modifier */
pj_scan_get_until_ch(scanner, ':', &bandw->modifier);
pj_scan_get_char(scanner);
/* value */
pj_scan_get_until_chr(scanner, " \t\r\n", &str);
bandw->value = pj_strtoul(&str);
/* We've got what we're looking for, skip anything until newline */
pj_scan_skip_line(scanner);
}
static void parse_media(pj_scanner *scanner, pjmedia_sdp_media *med,
parse_context *ctx)
{
pj_str_t str;
ctx->last_error = PJMEDIA_SDP_EINMEDIA;
/* check the equal sign */
if (*(scanner->curptr+1) != '=') {
on_scanner_error(scanner);
return;
}
/* m= */
pj_scan_advance_n(scanner, 2, SKIP_WS);
/* type */
pj_scan_get_until_ch(scanner, ' ', &med->desc.media);
pj_scan_get_char(scanner);
/* port */
pj_scan_get(scanner, &cs_token, &str);
med->desc.port = (unsigned short)pj_strtoul(&str);
if (*scanner->curptr == '/') {
/* port count */
pj_scan_get_char(scanner);
pj_scan_get(scanner, &cs_token, &str);
med->desc.port_count = pj_strtoul(&str);
} else {
med->desc.port_count = 0;
}
if (pj_scan_get_char(scanner) != ' ') {
PJ_THROW(SYNTAX_ERROR);
}
/* transport */
pj_scan_get_until_chr(scanner, " \t\r\n", &med->desc.transport);
/* format list */
med->desc.fmt_count = 0;
while (*scanner->curptr == ' ') {
pj_str_t fmt;
pj_scan_get_char(scanner);
/* Check again for the end of the line */
if ((*scanner->curptr == '\r') || (*scanner->curptr == '\n'))
break;
pj_scan_get(scanner, &cs_token, &fmt);
if (med->desc.fmt_count < PJMEDIA_MAX_SDP_FMT)
med->desc.fmt[med->desc.fmt_count++] = fmt;
else
PJ_PERROR(2,(THIS_FILE, PJ_ETOOMANY,
"Error adding SDP media format %.*s, "
"format is ignored",
(int)fmt.slen, fmt.ptr));
}
/* We've got what we're looking for, skip anything until newline */
pj_scan_skip_line(scanner);
}
static void on_scanner_error(pj_scanner *scanner)
{
PJ_UNUSED_ARG(scanner);
PJ_THROW(SYNTAX_ERROR);
}
static pjmedia_sdp_attr *parse_attr( pj_pool_t *pool, pj_scanner *scanner,
parse_context *ctx)
{
pjmedia_sdp_attr *attr;
ctx->last_error = PJMEDIA_SDP_EINATTR;
attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr);
/* check equal sign */
if (*(scanner->curptr+1) != '=') {
on_scanner_error(scanner);
return NULL;
}
/* skip a= */
pj_scan_advance_n(scanner, 2, SKIP_WS);
/* get attr name. */
pj_scan_get(scanner, &cs_token, &attr->name);
if (*scanner->curptr && *scanner->curptr != '\r' &&
*scanner->curptr != '\n')
{
/* skip ':' if present. */
if (*scanner->curptr == ':')
pj_scan_get_char(scanner);
/* get value */
if (*scanner->curptr != '\r' && *scanner->curptr != '\n') {
pj_scan_get_until_chr(scanner, "\r\n", &attr->value);
} else {
attr->value.ptr = NULL;
attr->value.slen = 0;
}
} else {
attr->value.ptr = NULL;
attr->value.slen = 0;
}
/* We've got what we're looking for, skip anything until newline */
pj_scan_skip_line(scanner);
return attr;
}
/*
* Apply direction attribute in session to all media.
*/
static void apply_media_direction(pjmedia_sdp_session *sdp)
{
pjmedia_sdp_attr *dir_attr = NULL;
unsigned i;
const pj_str_t inactive = { "inactive", 8 };
const pj_str_t sendonly = { "sendonly", 8 };
const pj_str_t recvonly = { "recvonly", 8 };
const pj_str_t sendrecv = { "sendrecv", 8 };
/* Find direction attribute in session, don't need to find default
* direction "sendrecv".
*/
for (i = 0; i < sdp->attr_count && !dir_attr; ++i) {
if (!pj_strcmp(&sdp->attr[i]->name, &sendonly) ||
!pj_strcmp(&sdp->attr[i]->name, &recvonly) ||
!pj_strcmp(&sdp->attr[i]->name, &inactive))
{
dir_attr = sdp->attr[i];
}
}
/* Found the direction attribute */
if (dir_attr) {
/* Remove the direction attribute in session */
pjmedia_sdp_attr_remove(&sdp->attr_count, sdp->attr, dir_attr);
/* Apply the direction attribute to all media, but not overriding it
* if media already has direction attribute.
*/
for (i = 0; i < sdp->media_count; ++i) {
pjmedia_sdp_media *m;
unsigned j;
/* Find direction attribute in this media */
m = sdp->media[i];
for (j = 0; j < m->attr_count; ++j) {
if (!pj_strcmp(&m->attr[j]->name, &sendrecv) ||
!pj_strcmp(&m->attr[j]->name, &sendonly) ||
!pj_strcmp(&m->attr[j]->name, &recvonly) ||
!pj_strcmp(&m->attr[j]->name, &inactive))
{
break;
}
}
/* Not found, apply direction attribute from session */
if (j == m->attr_count)
pjmedia_sdp_media_add_attr(m, dir_attr);
}
}
}
/*
* Parse SDP message.
*/
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;
pjmedia_sdp_session *session;
pjmedia_sdp_media *media = NULL;
pjmedia_sdp_attr *attr;
pjmedia_sdp_conn *conn;
pjmedia_sdp_bandw *bandw;
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_ZALLOC_T(pool, pjmedia_sdp_session);
PJ_ASSERT_RETURN(session != NULL, PJ_ENOMEM);
/* Ignore leading newlines */
while (*scanner.curptr=='\r' || *scanner.curptr=='\n')
pj_scan_get_char(&scanner);
PJ_TRY {
while (!pj_scan_is_eof(&scanner)) {
cur_name = *scanner.curptr;
switch (cur_name) {
case 'a':
attr = parse_attr(pool, &scanner, &ctx);
if (attr) {
if (media) {
pjmedia_sdp_media_add_attr(media, attr);
} else {
pjmedia_sdp_session_add_attr(session, attr);
}
}
break;
case 'o':
parse_origin(&scanner, session, &ctx);
break;
case 's':
parse_generic_line(&scanner, &session->name, &ctx);
break;
case 'c':
conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
parse_connection_info(&scanner, conn, &ctx);
if (media) {
media->conn = conn;
} else {
session->conn = conn;
}
break;
case 't':
parse_time(&scanner, session, &ctx);
break;
case 'm':
media = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
parse_media(&scanner, media, &ctx);
if (session->media_count < PJMEDIA_MAX_SDP_MEDIA)
session->media[ session->media_count++ ] = media;
else
PJ_PERROR(2,(THIS_FILE, PJ_ETOOMANY,
"Error adding media, media is ignored"));
break;
case 'v':
parse_version(&scanner, &ctx);
break;
case 13:
case 10:
pj_scan_get_char(&scanner);
/* Allow empty newlines at the end of the message */
while (!pj_scan_is_eof(&scanner)) {
if (*scanner.curptr != 13 && *scanner.curptr != 10) {
ctx.last_error = PJMEDIA_SDP_EINSDP;
on_scanner_error(&scanner);
}
pj_scan_get_char(&scanner);
}
break;
case 'b':
bandw = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_bandw);
parse_bandwidth_info(&scanner, bandw, &ctx);
if (media) {
media->bandw[media->bandw_count++] = bandw;
} else {
session->bandw[session->bandw_count++] = bandw;
}
break;
default:
if (cur_name >= 'a' && cur_name <= 'z')
parse_generic_line(&scanner, &dummy, &ctx);
else {
ctx.last_error = PJMEDIA_SDP_EINSDP;
on_scanner_error(&scanner);
}
break;
}
}
ctx.last_error = PJ_SUCCESS;
}
PJ_CATCH_ANY {
char errmsg[PJ_ERR_MSG_SIZE];
pj_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_assert(ctx.last_error != PJ_SUCCESS);
}
PJ_END;
pj_scan_fini(&scanner);
if (session)
apply_media_direction(session);
*p_sdp = session;
return ctx.last_error;
}
/*
* Print SDP description.
*/
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_T(pool, 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);
}
/* Duplicate bandwidth info */
sess->bandw_count = rhs->bandw_count;
for (i=0; i<rhs->bandw_count; ++i) {
sess->bandw[i] = pjmedia_sdp_bandw_clone(pool, rhs->bandw[i]);
}
/* 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)
{
return pjmedia_sdp_validate2(sdp, PJ_TRUE);
}
/* Validate SDP session descriptor. */
PJ_DEF(pj_status_t) pjmedia_sdp_validate2(const pjmedia_sdp_session *sdp,
pj_bool_t strict)
{
unsigned i;
const pj_str_t STR_RTPMAP = { "rtpmap", 6 };
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 || m->desc.port==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)
if (strict || m->desc.port != 0)
return PJMEDIA_SDP_EMISSINGCONN;
}
/* Verify payload type. */
for (j=0; j<m->desc.fmt_count; ++j) {
/* Arrgh noo!! Payload type can be non-numeric!!
* RTC based programs sends "null" for instant messaging!
*/
if (pj_isdigit(*m->desc.fmt[j].ptr)) {
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_attr(m, &STR_RTPMAP,
&m->desc.fmt[j]);
CHECK( a != NULL, PJMEDIA_SDP_EMISSINGRTPMAP);
}
}
}
}
/* Looks good. */
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjmedia_sdp_transport_cmp( const pj_str_t *t1,
const pj_str_t *t2)
{
static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 };
static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 };
/* Exactly equal? */
if (pj_stricmp(t1, t2) == 0)
return PJ_SUCCESS;
/* Compatible? */
if ((!pj_stricmp(t1, &ID_RTP_AVP) || !pj_stricmp(t1, &ID_RTP_SAVP)) &&
(!pj_stricmp(t2, &ID_RTP_AVP) || !pj_stricmp(t2, &ID_RTP_SAVP)))
return PJ_SUCCESS;
return PJMEDIA_SDP_ETPORTNOTEQUAL;
}
PJ_DEF(pj_status_t) pjmedia_sdp_media_deactivate(pj_pool_t *pool,
pjmedia_sdp_media *m)
{
PJ_ASSERT_RETURN(m, PJ_EINVAL);
PJ_UNUSED_ARG(pool);
/* Set port to zero */
m->desc.port = 0;
/* And remove attributes */
m->attr_count = 0;
return PJ_SUCCESS;
}
PJ_DEF(pjmedia_sdp_media*) pjmedia_sdp_media_clone_deactivate(
pj_pool_t *pool,
const pjmedia_sdp_media *rhs)
{
unsigned int i;
pjmedia_sdp_media *m;
PJ_ASSERT_RETURN(pool && rhs, NULL);
m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
pj_memcpy(m, rhs, sizeof(*m));
/* Clone the media line only */
pj_strdup (pool, &m->desc.media, &rhs->desc.media);
pj_strdup (pool, &m->desc.transport, &rhs->desc.transport);
for (i=0; i<rhs->desc.fmt_count; ++i)
pj_strdup(pool, &m->desc.fmt[i], &rhs->desc.fmt[i]);
if (rhs->conn) {
m->conn = pjmedia_sdp_conn_clone (pool, rhs->conn);
PJ_ASSERT_RETURN(m->conn != NULL, NULL);
}
m->bandw_count = rhs->bandw_count;
for (i=0; i < rhs->bandw_count; ++i) {
m->bandw[i] = pjmedia_sdp_bandw_clone (pool, rhs->bandw[i]);
PJ_ASSERT_RETURN(m->bandw[i] != NULL, NULL);
}
/* And deactivate it */
pjmedia_sdp_media_deactivate(pool, m);
return m;
}