| /* $Header: /pjproject/pjmedia/src/pjmedia/sdp.c 9 6/12/05 11:36a Bennylp $ */ |
| |
| |
| #include <pjmedia/sdp.h> |
| #include <pj/scanner.h> |
| #include <pj/except.h> |
| #include <pj/log.h> |
| #include <pj/os.h> |
| #include <pj/string.h> |
| #include <pj/pool.h> |
| |
| enum { |
| SKIP_WS = 0, |
| SYNTAX_ERROR = 1, |
| }; |
| #define TOKEN "-.!%*_=`'~" |
| #define NTP_OFFSET ((pj_uint32_t)2208988800) |
| #define LOG_THIS "sdp" |
| |
| /* |
| * 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); |
| |
| |
| /* |
| * Prototypes |
| */ |
| static void init_sdp_parser(void); |
| |
| |
| 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. |
| */ |
| static int is_initialized; |
| static pj_char_spec cs_token; |
| |
| static void init_sdp_parser(void) |
| { |
| if (is_initialized == 0) { |
| is_initialized = 1; |
| if (is_initialized != 1) { |
| return; |
| } |
| } |
| pj_cs_add_alpha(cs_token); |
| pj_cs_add_num(cs_token); |
| pj_cs_add_str( cs_token, TOKEN); |
| } |
| |
| static int print_rtpmap_attr(const pjsdp_rtpmap_attr *rtpmap, |
| char *buf, int len) |
| { |
| 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; |
| |
| /* space, encoding name */ |
| *p++ = ' '; |
| pj_memcpy(p, rtpmap->encoding_name.ptr, rtpmap->encoding_name.slen); |
| p += rtpmap->encoding_name.slen; |
| |
| /* slash, clock-rate. */ |
| *p++ = '/'; |
| len = pj_utoa(rtpmap->clock_rate, p); |
| p += len; |
| |
| /* optionally add encoding parameter. */ |
| if (rtpmap->parameter.slen) { |
| *p++ = '/'; |
| pj_memcpy(p, rtpmap->parameter.ptr, rtpmap->parameter.slen); |
| p += rtpmap->parameter.slen; |
| } |
| |
| return p-buf; |
| } |
| |
| static int print_generic_string_attr(const pjsdp_attr_string *attr, |
| char *buf, int len) |
| { |
| char *p = buf; |
| |
| if (len < attr->value.slen + 4) { |
| return -1; |
| } |
| |
| /* colon and attribute value. */ |
| *p++ = ':'; |
| pj_memcpy(p, attr->value.ptr, attr->value.slen); |
| p += attr->value.slen; |
| |
| return p-buf; |
| } |
| |
| static int print_generic_num_attr(const pjsdp_attr_num *attr, char *buf, int len) |
| { |
| char *p = buf; |
| |
| if (len < 10) { |
| return -1; |
| } |
| *p++ = ':'; |
| return pj_utoa(attr->value, p); |
| } |
| |
| static int print_name_only_attr(const pjsdp_attr *attr, char *buf, int len) |
| { |
| PJ_UNUSED_ARG(attr) |
| PJ_UNUSED_ARG(buf) |
| PJ_UNUSED_ARG(len) |
| return 0; |
| } |
| |
| static int print_fmtp_attr(const pjsdp_fmtp_attr *fmtp, char *buf, int len) |
| { |
| char *p = buf; |
| |
| if (len < 4+fmtp->format.slen+fmtp->param.slen) { |
| return -1; |
| } |
| |
| /* colon and format. */ |
| *p++ = ':'; |
| pj_memcpy(p, fmtp->format.ptr, fmtp->format.slen); |
| p += fmtp->format.slen; |
| |
| /* space and parameter. */ |
| *p++ = ' '; |
| pj_memcpy(p, fmtp->param.ptr, fmtp->param.slen); |
| p += fmtp->param.slen; |
| |
| return p-buf; |
| } |
| |
| |
| static int print_attr(const pjsdp_attr *attr, char *buf, int len) |
| { |
| char *p = buf; |
| struct attr_map_rec *desc = &attr_map[attr->type]; |
| |
| if (len < 16) { |
| return -1; |
| } |
| |
| *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; |
| } |
| |
| static pjsdp_attr* clone_rtpmap_attr (pj_pool_t *pool, const pjsdp_attr *p) |
| { |
| 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; |
| |
| 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 (pjsdp_attr*)attr; |
| } |
| |
| static pjsdp_attr* clone_generic_string_attr (pj_pool_t *pool, const pjsdp_attr *p) |
| { |
| 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; |
| |
| attr->type = rhs->type; |
| if (!pj_strdup (pool, &attr->value, &rhs->value)) return NULL; |
| |
| return (pjsdp_attr*)attr; |
| } |
| |
| 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; |
| |
| attr->type = rhs->type; |
| attr->value = rhs->value; |
| |
| return (pjsdp_attr*)attr; |
| } |
| |
| 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; |
| |
| attr->type = rhs->type; |
| return attr; |
| } |
| |
| 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; |
| |
| attr->type = rhs->type; |
| if (!pj_strdup (pool, &attr->format, &rhs->format)) return NULL; |
| if (!pj_strdup (pool, &attr->param, &rhs->param)) return NULL; |
| |
| return (pjsdp_attr*)attr; |
| } |
| |
| PJ_DEF(pjsdp_attr*) pjsdp_attr_clone (pj_pool_t *pool, const pjsdp_attr *rhs) |
| { |
| struct attr_map_rec *desc; |
| |
| if (rhs->type >= PJSDP_END_OF_ATTR) { |
| pj_assert(0); |
| return NULL; |
| } |
| |
| desc = &attr_map[rhs->type]; |
| return (*desc->clone) (pool, rhs); |
| } |
| |
| PJ_DEF(const pjsdp_attr*) pjsdp_attr_find (int count, const pjsdp_attr *attr_array[], int type) |
| { |
| int i; |
| |
| for (i=0; i<count; ++i) { |
| if (attr_array[i]->type == type) |
| return attr_array[i]; |
| } |
| return NULL; |
| } |
| |
| static int print_connection_info( pjsdp_conn_info *c, char *buf, int len) |
| { |
| char *p = buf; |
| |
| if (len < 8+c->net_type.slen+c->addr_type.slen+c->addr.slen) { |
| return -1; |
| } |
| *p++ = 'c'; |
| *p++ = '='; |
| pj_memcpy(p, c->net_type.ptr, c->net_type.slen); |
| p += c->net_type.slen; |
| *p++ = ' '; |
| pj_memcpy(p, c->addr_type.ptr, c->addr_type.slen); |
| p += c->addr_type.slen; |
| *p++ = ' '; |
| pj_memcpy(p, c->addr.ptr, c->addr.slen); |
| p += c->addr.slen; |
| *p++ = '\r'; |
| *p++ = '\n'; |
| |
| return p-buf; |
| } |
| |
| PJ_DEF(pjsdp_conn_info*) pjsdp_conn_info_clone (pj_pool_t *pool, const pjsdp_conn_info *rhs) |
| { |
| pjsdp_conn_info *c = pj_pool_alloc (pool, sizeof(pjsdp_conn_info)); |
| 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; |
| } |
| |
| static int print_media_desc( pjsdp_media_desc *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, end-p); |
| if (printed < 0) { |
| return -1; |
| } |
| p += printed; |
| } |
| |
| /* print attributes. */ |
| for (i=0; i<m->attr_count; ++i) { |
| printed = print_attr(m->attr[i], p, end-p); |
| if (printed < 0) { |
| return -1; |
| } |
| p += printed; |
| } |
| |
| return p-buf; |
| } |
| |
| PJ_DEF(pjsdp_media_desc*) pjsdp_media_desc_clone (pj_pool_t *pool, |
| const pjsdp_media_desc *rhs) |
| { |
| unsigned int i; |
| pjsdp_media_desc *m = pj_pool_alloc (pool, sizeof(pjsdp_media_desc)); |
| if (!m) |
| return 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) |
| 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; |
| } 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; |
| } |
| |
| 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) |
| { |
| 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; |
| } |
| |
| /** 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) |
| { |
| 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; |
| } |
| |
| |
| static int print_session(const pjsdp_session_desc *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'; |
| |
| /* 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'; |
| |
| /* Connection line (c=) if exist. */ |
| if (ses->conn) { |
| printed = print_connection_info(ses->conn, p, end-p); |
| if (printed < 1) { |
| return -1; |
| } |
| p += printed; |
| } |
| |
| /* Print all attribute (a=) lines. */ |
| for (i=0; i<ses->attr_count; ++i) { |
| printed = 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, end-p); |
| if (printed < 0) { |
| return -1; |
| } |
| p += printed; |
| } |
| |
| return p-buf; |
| } |
| |
| /****************************************************************************** |
| * PARSERS |
| */ |
| |
| static void parse_version(pj_scanner *scanner) |
| { |
| pj_scan_advance_n(scanner, 3, SKIP_WS); |
| pj_scan_get_newline(scanner); |
| } |
| |
| static void parse_origin(pj_scanner *scanner, pjsdp_session_desc *ses) |
| { |
| pj_str_t str; |
| |
| /* 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_ch(scanner, '\r', &ses->origin.addr); |
| |
| /* newline */ |
| pj_scan_get_newline(scanner); |
| } |
| |
| static void parse_time(pj_scanner *scanner, pjsdp_session_desc *ses) |
| { |
| pj_str_t str; |
| |
| /* 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_ch(scanner, '\r', &str); |
| ses->time.stop = pj_strtoul(&str); |
| |
| /* newline */ |
| pj_scan_get_newline(scanner); |
| } |
| |
| static void parse_generic_line(pj_scanner *scanner, pj_str_t *str) |
| { |
| /* x= */ |
| pj_scan_advance_n(scanner, 2, SKIP_WS); |
| |
| /* get anything until newline. */ |
| pj_scan_get_until_ch(scanner, '\r', str); |
| |
| /* newline. */ |
| pj_scan_get_newline(scanner); |
| } |
| |
| static void parse_connection_info(pj_scanner *scanner, pjsdp_conn_info *conn) |
| { |
| /* 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_ch(scanner, '\r', &conn->addr); |
| |
| /* newline */ |
| pj_scan_get_newline(scanner); |
| } |
| |
| static void parse_media(pj_scanner *scanner, pjsdp_media_desc *med) |
| { |
| pj_str_t str; |
| |
| /* 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->current == '/') { |
| /* 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_ch(scanner, ' ', &med->desc.transport); |
| |
| /* format list */ |
| med->desc.fmt_count = 0; |
| while (*scanner->current == ' ') { |
| pj_scan_get_char(scanner); |
| pj_scan_get(scanner, cs_token, &med->desc.fmt[med->desc.fmt_count++]); |
| } |
| |
| /* newline */ |
| 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->current == '/') { |
| 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 ) |
| { |
| pjsdp_attr *attr; |
| |
| PJ_UNUSED_ARG(scanner) |
| attr = pj_pool_calloc(pool, 1, sizeof(*attr)); |
| return attr; |
| } |
| |
| static pjsdp_fmtp_attr * parse_fmtp_attr( pj_pool_t *pool, pj_scanner *scanner ) |
| { |
| pjsdp_fmtp_attr *fmtp; |
| |
| 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; |
| } |
| |
| 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; |
| |
| /* 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; |
| } |
| } |
| |
| /* fallback to generic string parser. */ |
| if (parse_func == NULL) { |
| parse_func = &parse_generic_string_attr; |
| } |
| |
| 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_scanner scanner; |
| pjsdp_session_desc *session; |
| pjsdp_media_desc *media = NULL; |
| void *attr; |
| pjsdp_conn_info *conn; |
| pj_str_t dummy; |
| int cur_name = 254; |
| PJ_USE_EXCEPTION; |
| |
| init_sdp_parser(); |
| |
| pj_scan_init(&scanner, buf, len, 0, &on_scanner_error); |
| session = pj_pool_calloc(pool, 1, sizeof(*session)); |
| |
| PJ_TRY { |
| while (!pj_scan_is_eof(&scanner)) { |
| cur_name = *scanner.current; |
| switch (cur_name) { |
| case 'a': |
| attr = parse_attr(pool, &scanner); |
| if (attr) { |
| if (media) { |
| media->attr[media->attr_count++] = attr; |
| } else { |
| session->attr[session->attr_count++] = attr; |
| } |
| } |
| break; |
| case 'o': |
| parse_origin(&scanner, session); |
| break; |
| case 's': |
| parse_generic_line(&scanner, &session->name); |
| break; |
| case 'c': |
| conn = pj_pool_calloc(pool, 1, sizeof(*conn)); |
| parse_connection_info(&scanner, conn); |
| if (media) { |
| media->conn = conn; |
| } else { |
| session->conn = conn; |
| } |
| break; |
| case 't': |
| parse_time(&scanner, session); |
| break; |
| case 'm': |
| media = pj_pool_calloc(pool, 1, sizeof(*media)); |
| parse_media(&scanner, media); |
| session->media[ session->media_count++ ] = media; |
| break; |
| case 'v': |
| parse_version(&scanner); |
| break; |
| default: |
| parse_generic_line(&scanner, &dummy); |
| break; |
| } |
| } |
| } |
| PJ_CATCH(SYNTAX_ERROR) { |
| PJ_LOG(2, (LOG_THIS, "Syntax error in SDP parser '%c' line %d col %d", |
| cur_name, scanner.line, scanner.col)); |
| if (!pj_scan_is_eof(&scanner)) { |
| if (*scanner.current != '\r') { |
| pj_scan_get_until_ch(&scanner, '\r', &dummy); |
| } |
| pj_scan_get_newline(&scanner); |
| } |
| } |
| PJ_END; |
| |
| pj_scan_fini(&scanner); |
| return session; |
| } |
| |
| /* |
| * Print SDP description. |
| */ |
| PJ_DEF(int) pjsdp_print( const pjsdp_session_desc *desc, char *buf, pj_size_t size) |
| { |
| return print_session(desc, buf, size); |
| } |
| |
| |