blob: 4155f6a4ed695caf34b03df3d069fb962c9082c0 [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001/* $Id$ */
2/*
3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20#include <pjmedia/sdp.h>
21#include <pjmedia/errno.h>
22#include <pjlib-util/scanner.h>
23#include <pj/array.h>
24#include <pj/except.h>
25#include <pj/log.h>
26#include <pj/os.h>
27#include <pj/string.h>
28#include <pj/pool.h>
29#include <pj/assert.h>
30#include <pj/ctype.h>
31
32
33enum {
34 SKIP_WS = 0,
35 SYNTAX_ERROR = 1,
36};
37// New token definition from RFC 4566 (SDP)
38#define TOKEN "!#$%&'*+-.^_`{|}~"
39//#define TOKEN "-.!%*_=`'~"
40//#define TOKEN "'`-./:?\"#$&*;=@[]^_`{|}+~!"
41#define NTP_OFFSET ((pj_uint32_t)2208988800)
42#define THIS_FILE "sdp.c"
43
44typedef struct parse_context
45{
46 pj_status_t last_error;
47} parse_context;
48
49
50/*
51 * Prototypes for line parser.
52 */
53static void parse_version(pj_scanner *scanner, parse_context *ctx);
54static void parse_origin(pj_scanner *scanner, pjmedia_sdp_session *ses,
55 parse_context *ctx);
56static void parse_time(pj_scanner *scanner, pjmedia_sdp_session *ses,
57 parse_context *ctx);
58static void parse_generic_line(pj_scanner *scanner, pj_str_t *str,
59 parse_context *ctx);
60static void parse_connection_info(pj_scanner *scanner, pjmedia_sdp_conn *conn,
61 parse_context *ctx);
62static void parse_bandwidth_info(pj_scanner *scanner, pjmedia_sdp_bandw *bandw,
63 parse_context *ctx);
64static pjmedia_sdp_attr *parse_attr(pj_pool_t *pool, pj_scanner *scanner,
65 parse_context *ctx);
66static void parse_media(pj_scanner *scanner, pjmedia_sdp_media *med,
67 parse_context *ctx);
68static void on_scanner_error(pj_scanner *scanner);
69
70/*
71 * Scanner character specification.
72 */
73static int is_initialized;
74static pj_cis_buf_t cis_buf;
75static pj_cis_t cs_digit, cs_token;
76
77static void init_sdp_parser(void)
78{
79 if (is_initialized != 0)
80 return;
81
82 pj_enter_critical_section();
83
84 if (is_initialized != 0) {
85 pj_leave_critical_section();
86 return;
87 }
88
89 pj_cis_buf_init(&cis_buf);
90
91 pj_cis_init(&cis_buf, &cs_token);
92 pj_cis_add_alpha(&cs_token);
93 pj_cis_add_num(&cs_token);
94 pj_cis_add_str(&cs_token, TOKEN);
95
96 pj_cis_init(&cis_buf, &cs_digit);
97 pj_cis_add_num(&cs_digit);
98
99 is_initialized = 1;
100 pj_leave_critical_section();
101}
102
103PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_create( pj_pool_t *pool,
104 const char *name,
105 const pj_str_t *value)
106{
107 pjmedia_sdp_attr *attr;
108
109 PJ_ASSERT_RETURN(pool && name, NULL);
110
111 attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr);
112 pj_strdup2(pool, &attr->name, name);
113
114 if (value)
115 pj_strdup_with_null(pool, &attr->value, value);
116 else {
117 attr->value.ptr = NULL;
118 attr->value.slen = 0;
119 }
120
121 return attr;
122}
123
124PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_clone(pj_pool_t *pool,
125 const pjmedia_sdp_attr *rhs)
126{
127 pjmedia_sdp_attr *attr;
128
129 PJ_ASSERT_RETURN(pool && rhs, NULL);
130
131 attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr);
132
133 pj_strdup(pool, &attr->name, &rhs->name);
134 pj_strdup_with_null(pool, &attr->value, &rhs->value);
135
136 return attr;
137}
138
139PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_find (unsigned count,
140 pjmedia_sdp_attr *const attr_array[],
141 const pj_str_t *name,
142 const pj_str_t *c_fmt)
143{
144 unsigned i;
145 unsigned c_pt = 0xFFFF;
146
147 if (c_fmt)
148 c_pt = pj_strtoul(c_fmt);
149
150 for (i=0; i<count; ++i) {
151 if (pj_strcmp(&attr_array[i]->name, name) == 0) {
152 const pjmedia_sdp_attr *a = attr_array[i];
153 if (c_fmt) {
154 unsigned pt = (unsigned) pj_strtoul2(&a->value, NULL, 10);
155 if (pt == c_pt) {
156 return (pjmedia_sdp_attr*)a;
157 }
158 } else
159 return (pjmedia_sdp_attr*)a;
160 }
161 }
162 return NULL;
163}
164
165PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_find2(unsigned count,
166 pjmedia_sdp_attr *const attr_array[],
167 const char *c_name,
168 const pj_str_t *c_fmt)
169{
170 pj_str_t name;
171
172 name.ptr = (char*)c_name;
173 name.slen = pj_ansi_strlen(c_name);
174
175 return pjmedia_sdp_attr_find(count, attr_array, &name, c_fmt);
176}
177
178
179
180PJ_DEF(pj_status_t) pjmedia_sdp_attr_add(unsigned *count,
181 pjmedia_sdp_attr *attr_array[],
182 pjmedia_sdp_attr *attr)
183{
184 PJ_ASSERT_RETURN(count && attr_array && attr, PJ_EINVAL);
185 PJ_ASSERT_RETURN(*count < PJMEDIA_MAX_SDP_ATTR, PJ_ETOOMANY);
186
187 attr_array[*count] = attr;
188 (*count)++;
189
190 return PJ_SUCCESS;
191}
192
193
194PJ_DEF(unsigned) pjmedia_sdp_attr_remove_all(unsigned *count,
195 pjmedia_sdp_attr *attr_array[],
196 const char *name)
197{
198 unsigned i, removed = 0;
199 pj_str_t attr_name;
200
201 PJ_ASSERT_RETURN(count && attr_array && name, PJ_EINVAL);
202
203 attr_name.ptr = (char*)name;
204 attr_name.slen = pj_ansi_strlen(name);
205
206 for (i=0; i<*count; ) {
207 if (pj_strcmp(&attr_array[i]->name, &attr_name)==0) {
208 pj_array_erase(attr_array, sizeof(pjmedia_sdp_attr*),
209 *count, i);
210 --(*count);
211 ++removed;
212 } else {
213 ++i;
214 }
215 }
216
217 return removed;
218}
219
220
221PJ_DEF(pj_status_t) pjmedia_sdp_attr_remove( unsigned *count,
222 pjmedia_sdp_attr *attr_array[],
223 pjmedia_sdp_attr *attr )
224{
225 unsigned i, removed=0;
226
227 PJ_ASSERT_RETURN(count && attr_array && attr, PJ_EINVAL);
228
229 for (i=0; i<*count; ) {
230 if (attr_array[i] == attr) {
231 pj_array_erase(attr_array, sizeof(pjmedia_sdp_attr*),
232 *count, i);
233 --(*count);
234 ++removed;
235 } else {
236 ++i;
237 }
238 }
239
240 return removed ? PJ_SUCCESS : PJ_ENOTFOUND;
241}
242
243
244PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_rtpmap( const pjmedia_sdp_attr *attr,
245 pjmedia_sdp_rtpmap *rtpmap)
246{
247 pj_scanner scanner;
248 pj_str_t token;
249 pj_status_t status = -1;
250 char term = 0;
251 PJ_USE_EXCEPTION;
252
253 PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "rtpmap")==0, PJ_EINVALIDOP);
254
255 PJ_ASSERT_RETURN(attr->value.slen != 0, PJMEDIA_SDP_EINATTR);
256
257 init_sdp_parser();
258
259 /* Check if input is null terminated, and null terminate if
260 * necessary. Unfortunately this may crash the application if
261 * attribute was allocated from a read-only memory location.
262 * But this shouldn't happen as attribute's value normally is
263 * null terminated.
264 */
265 if (attr->value.ptr[attr->value.slen] != 0 &&
266 attr->value.ptr[attr->value.slen] != '\r' &&
267 attr->value.ptr[attr->value.slen] != '\n')
268 {
269 pj_assert(!"Shouldn't happen");
270 term = attr->value.ptr[attr->value.slen];
271 attr->value.ptr[attr->value.slen] = '\0';
272 }
273
274 pj_scan_init(&scanner, (char*)attr->value.ptr, attr->value.slen,
275 PJ_SCAN_AUTOSKIP_WS, &on_scanner_error);
276
277 /* rtpmap sample:
278 * a=rtpmap:98 L16/16000/2.
279 */
280
281 /* Init */
282 rtpmap->pt.slen = rtpmap->param.slen = rtpmap->enc_name.slen = 0;
283 rtpmap->clock_rate = 0;
284
285 /* Parse */
286 PJ_TRY {
287
288 /* Get payload type. */
289 pj_scan_get(&scanner, &cs_token, &rtpmap->pt);
290
291
292 /* Get encoding name. */
293 pj_scan_get(&scanner, &cs_token, &rtpmap->enc_name);
294
295 /* Expecting '/' after encoding name. */
296 if (pj_scan_get_char(&scanner) != '/') {
297 status = PJMEDIA_SDP_EINRTPMAP;
298 goto on_return;
299 }
300
301
302 /* Get the clock rate. */
303 pj_scan_get(&scanner, &cs_digit, &token);
304 rtpmap->clock_rate = pj_strtoul(&token);
305
306 /* Expecting either '/' or EOF */
307 if (*scanner.curptr == '/') {
308 pj_scan_get_char(&scanner);
309 rtpmap->param.ptr = scanner.curptr;
310 rtpmap->param.slen = scanner.end - scanner.curptr;
311 } else {
312 rtpmap->param.slen = 0;
313 }
314
315 status = PJ_SUCCESS;
316 }
317 PJ_CATCH_ANY {
318 status = PJMEDIA_SDP_EINRTPMAP;
319 }
320 PJ_END;
321
322
323on_return:
324 pj_scan_fini(&scanner);
325 if (term) {
326 attr->value.ptr[attr->value.slen] = term;
327 }
328 return status;
329}
330
331PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_fmtp( const pjmedia_sdp_attr *attr,
332 pjmedia_sdp_fmtp *fmtp)
333{
334 const char *p = attr->value.ptr;
335 const char *end = attr->value.ptr + attr->value.slen;
336 pj_str_t token;
337
338 PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "fmtp")==0, PJ_EINVALIDOP);
339
340 /* fmtp BNF:
341 * a=fmtp:<format> <format specific parameter>
342 */
343
344 /* Get format. */
345 token.ptr = (char*)p;
346 while (pj_isdigit(*p) && p!=end)
347 ++p;
348 token.slen = p - token.ptr;
349 if (token.slen == 0)
350 return PJMEDIA_SDP_EINFMTP;
351
352 fmtp->fmt = token;
353
354 /* Expecting space after format. */
355 if (*p != ' ') return PJMEDIA_SDP_EINFMTP;
356
357 /* Get space. */
358 ++p;
359
360 /* Set the remaining string as fmtp format parameter. */
361 fmtp->fmt_param.ptr = (char*)p;
362 fmtp->fmt_param.slen = end - p;
363
364 return PJ_SUCCESS;
365}
366
367
368PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_rtcp(const pjmedia_sdp_attr *attr,
369 pjmedia_sdp_rtcp_attr *rtcp)
370{
371 pj_scanner scanner;
372 pj_str_t token;
373 pj_status_t status = -1;
374 PJ_USE_EXCEPTION;
375
376 PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "rtcp")==0, PJ_EINVALIDOP);
377
378 init_sdp_parser();
379
380 /* fmtp BNF:
381 * a=rtcp:<port> [nettype addrtype address]
382 */
383
384 pj_scan_init(&scanner, (char*)attr->value.ptr, attr->value.slen,
385 PJ_SCAN_AUTOSKIP_WS, &on_scanner_error);
386
387 /* Init */
388 rtcp->net_type.slen = rtcp->addr_type.slen = rtcp->addr.slen = 0;
389
390 /* Parse */
391 PJ_TRY {
392
393 /* Get the port */
394 pj_scan_get(&scanner, &cs_token, &token);
395 rtcp->port = pj_strtoul(&token);
396
397 /* Have address? */
398 if (!pj_scan_is_eof(&scanner)) {
399
400 /* Get network type */
401 pj_scan_get(&scanner, &cs_token, &rtcp->net_type);
402
403 /* Get address type */
404 pj_scan_get(&scanner, &cs_token, &rtcp->addr_type);
405
406 /* Get the address */
407 pj_scan_get(&scanner, &cs_token, &rtcp->addr);
408
409 }
410
411 status = PJ_SUCCESS;
412
413 }
414 PJ_CATCH_ANY {
415 status = PJMEDIA_SDP_EINRTCP;
416 }
417 PJ_END;
418
419 pj_scan_fini(&scanner);
420 return status;
421}
422
423
424PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_create_rtcp(pj_pool_t *pool,
425 const pj_sockaddr *a)
426{
427 enum {
428 ATTR_LEN = PJ_INET6_ADDRSTRLEN+16
429 };
430 pjmedia_sdp_attr *attr;
431
432 attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr);
433 attr->name = pj_str("rtcp");
434 attr->value.ptr = (char*) pj_pool_alloc(pool, ATTR_LEN);
435 if (a->addr.sa_family == pj_AF_INET()) {
436 attr->value.slen =
437 pj_ansi_snprintf(attr->value.ptr, ATTR_LEN,
438 "%u IN IP4 %s",
439 pj_ntohs(a->ipv4.sin_port),
440 pj_inet_ntoa(a->ipv4.sin_addr));
441 } else if (a->addr.sa_family == pj_AF_INET6()) {
442 char tmp_addr[PJ_INET6_ADDRSTRLEN];
443 attr->value.slen =
444 pj_ansi_snprintf(attr->value.ptr, ATTR_LEN,
445 "%u IN IP6 %s",
446 pj_sockaddr_get_port(a),
447 pj_sockaddr_print(a, tmp_addr,
448 sizeof(tmp_addr), 0));
449
450 } else {
451 pj_assert(!"Unsupported address family");
452 return NULL;
453 }
454
455 return attr;
456}
457
458
459PJ_DEF(pj_status_t) pjmedia_sdp_attr_to_rtpmap(pj_pool_t *pool,
460 const pjmedia_sdp_attr *attr,
461 pjmedia_sdp_rtpmap **p_rtpmap)
462{
463 PJ_ASSERT_RETURN(pool && attr && p_rtpmap, PJ_EINVAL);
464
465 *p_rtpmap = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_rtpmap);
466 PJ_ASSERT_RETURN(*p_rtpmap, PJ_ENOMEM);
467
468 return pjmedia_sdp_attr_get_rtpmap(attr, *p_rtpmap);
469}
470
471
472PJ_DEF(pj_status_t) pjmedia_sdp_rtpmap_to_attr(pj_pool_t *pool,
473 const pjmedia_sdp_rtpmap *rtpmap,
474 pjmedia_sdp_attr **p_attr)
475{
476 pjmedia_sdp_attr *attr;
477 char tempbuf[128];
478 int len;
479
480 /* Check arguments. */
481 PJ_ASSERT_RETURN(pool && rtpmap && p_attr, PJ_EINVAL);
482
483 /* Check that mandatory attributes are specified. */
484 PJ_ASSERT_RETURN(rtpmap->enc_name.slen && rtpmap->clock_rate,
485 PJMEDIA_SDP_EINRTPMAP);
486
487
488 attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr);
489 PJ_ASSERT_RETURN(attr != NULL, PJ_ENOMEM);
490
491 attr->name.ptr = "rtpmap";
492 attr->name.slen = 6;
493
494 /* Format: ":pt enc_name/clock_rate[/param]" */
495 len = pj_ansi_snprintf(tempbuf, sizeof(tempbuf),
496 "%.*s %.*s/%u%s%.*s",
497 (int)rtpmap->pt.slen,
498 rtpmap->pt.ptr,
499 (int)rtpmap->enc_name.slen,
500 rtpmap->enc_name.ptr,
501 rtpmap->clock_rate,
502 (rtpmap->param.slen ? "/" : ""),
503 (int)rtpmap->param.slen,
504 rtpmap->param.ptr);
505
506 if (len < 1 || len > (int)sizeof(tempbuf))
507 return PJMEDIA_SDP_ERTPMAPTOOLONG;
508
509 attr->value.slen = len;
510 attr->value.ptr = (char*) pj_pool_alloc(pool, attr->value.slen+1);
511 pj_memcpy(attr->value.ptr, tempbuf, attr->value.slen+1);
512
513 *p_attr = attr;
514 return PJ_SUCCESS;
515}
516
517
518static int print_connection_info( pjmedia_sdp_conn *c, char *buf, int len)
519{
520 int printed;
521
522 printed = pj_ansi_snprintf(buf, len, "c=%.*s %.*s %.*s\r\n",
523 (int)c->net_type.slen,
524 c->net_type.ptr,
525 (int)c->addr_type.slen,
526 c->addr_type.ptr,
527 (int)c->addr.slen,
528 c->addr.ptr);
529 if (printed < 1 || printed > len)
530 return -1;
531
532 return printed;
533}
534
535
536PJ_DEF(pjmedia_sdp_conn*) pjmedia_sdp_conn_clone (pj_pool_t *pool,
537 const pjmedia_sdp_conn *rhs)
538{
539 pjmedia_sdp_conn *c = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_conn);
540 if (!c) return NULL;
541
542 if (!pj_strdup (pool, &c->net_type, &rhs->net_type)) return NULL;
543 if (!pj_strdup (pool, &c->addr_type, &rhs->addr_type)) return NULL;
544 if (!pj_strdup (pool, &c->addr, &rhs->addr)) return NULL;
545
546 return c;
547}
548
549PJ_DEF(pjmedia_sdp_bandw*)
550pjmedia_sdp_bandw_clone (pj_pool_t *pool,
551 const pjmedia_sdp_bandw *rhs)
552{
553 pjmedia_sdp_bandw *b = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_bandw);
554 if (!b) return NULL;
555
556 if (!pj_strdup (pool, &b->modifier, &rhs->modifier)) return NULL;
557 b->value = rhs->value;
558
559 return b;
560}
561
562static pj_ssize_t print_bandw(const pjmedia_sdp_bandw *bandw,
563 char *buf, pj_size_t len)
564{
565 char *p = buf;
566
567 if ((int)len < bandw->modifier.slen + 10 + 5)
568 return -1;
569
570 *p++ = 'b';
571 *p++ = '=';
572 pj_memcpy(p, bandw->modifier.ptr, bandw->modifier.slen);
573 p += bandw->modifier.slen;
574 *p++ = ':';
575 p += pj_utoa(bandw->value, p);
576
577 *p++ = '\r';
578 *p++ = '\n';
579 return p-buf;
580}
581
582static pj_ssize_t print_attr(const pjmedia_sdp_attr *attr,
583 char *buf, pj_size_t len)
584{
585 char *p = buf;
586
587 if ((int)len < attr->name.slen + attr->value.slen + 10)
588 return -1;
589
590 *p++ = 'a';
591 *p++ = '=';
592 pj_memcpy(p, attr->name.ptr, attr->name.slen);
593 p += attr->name.slen;
594
595
596 if (attr->value.slen) {
597 *p++ = ':';
598 pj_memcpy(p, attr->value.ptr, attr->value.slen);
599 p += attr->value.slen;
600 }
601
602 *p++ = '\r';
603 *p++ = '\n';
604 return p-buf;
605}
606
607static int print_media_desc( pjmedia_sdp_media *m, char *buf, int len)
608{
609 char *p = buf;
610 char *end = buf+len;
611 unsigned i;
612 int printed;
613
614 /* check length for the "m=" line. */
615 if (len < m->desc.media.slen+m->desc.transport.slen+12+24) {
616 return -1;
617 }
618 *p++ = 'm'; /* m= */
619 *p++ = '=';
620 pj_memcpy(p, m->desc.media.ptr, m->desc.media.slen);
621 p += m->desc.media.slen;
622 *p++ = ' ';
623 printed = pj_utoa(m->desc.port, p);
624 p += printed;
625 if (m->desc.port_count > 1) {
626 *p++ = '/';
627 printed = pj_utoa(m->desc.port_count, p);
628 p += printed;
629 }
630 *p++ = ' ';
631 pj_memcpy(p, m->desc.transport.ptr, m->desc.transport.slen);
632 p += m->desc.transport.slen;
633 for (i=0; i<m->desc.fmt_count; ++i) {
634 *p++ = ' ';
635 pj_memcpy(p, m->desc.fmt[i].ptr, m->desc.fmt[i].slen);
636 p += m->desc.fmt[i].slen;
637 }
638 *p++ = '\r';
639 *p++ = '\n';
640
641 /* print connection info, if present. */
642 if (m->conn) {
643 printed = print_connection_info(m->conn, p, (int)(end-p));
644 if (printed < 0) {
645 return -1;
646 }
647 p += printed;
648 }
649
650 /* print optional bandwidth info. */
651 for (i=0; i<m->bandw_count; ++i) {
652 printed = (int)print_bandw(m->bandw[i], p, end-p);
653 if (printed < 0) {
654 return -1;
655 }
656 p += printed;
657 }
658
659 /* print attributes. */
660 for (i=0; i<m->attr_count; ++i) {
661 printed = (int)print_attr(m->attr[i], p, end-p);
662 if (printed < 0) {
663 return -1;
664 }
665 p += printed;
666 }
667
668 return (int)(p-buf);
669}
670
671PJ_DEF(pjmedia_sdp_media*) pjmedia_sdp_media_clone(
672 pj_pool_t *pool,
673 const pjmedia_sdp_media *rhs)
674{
675 unsigned int i;
676 pjmedia_sdp_media *m = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_media);
677 PJ_ASSERT_RETURN(m != NULL, NULL);
678
679 pj_strdup (pool, &m->desc.media, &rhs->desc.media);
680 m->desc.port = rhs->desc.port;
681 m->desc.port_count = rhs->desc.port_count;
682 pj_strdup (pool, &m->desc.transport, &rhs->desc.transport);
683 m->desc.fmt_count = rhs->desc.fmt_count;
684 for (i=0; i<rhs->desc.fmt_count; ++i)
685 pj_strdup(pool, &m->desc.fmt[i], &rhs->desc.fmt[i]);
686
687 if (rhs->conn) {
688 m->conn = pjmedia_sdp_conn_clone (pool, rhs->conn);
689 PJ_ASSERT_RETURN(m->conn != NULL, NULL);
690 } else {
691 m->conn = NULL;
692 }
693
694 m->bandw_count = rhs->bandw_count;
695 for (i=0; i < rhs->bandw_count; ++i) {
696 m->bandw[i] = pjmedia_sdp_bandw_clone (pool, rhs->bandw[i]);
697 PJ_ASSERT_RETURN(m->bandw[i] != NULL, NULL);
698 }
699
700 m->attr_count = rhs->attr_count;
701 for (i=0; i < rhs->attr_count; ++i) {
702 m->attr[i] = pjmedia_sdp_attr_clone (pool, rhs->attr[i]);
703 PJ_ASSERT_RETURN(m->attr[i] != NULL, NULL);
704 }
705
706 return m;
707}
708
709PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_media_find_attr(
710 const pjmedia_sdp_media *m,
711 const pj_str_t *name, const pj_str_t *fmt)
712{
713 PJ_ASSERT_RETURN(m && name, NULL);
714 return pjmedia_sdp_attr_find(m->attr_count, m->attr, name, fmt);
715}
716
717
718
719PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_media_find_attr2(
720 const pjmedia_sdp_media *m,
721 const char *name, const pj_str_t *fmt)
722{
723 PJ_ASSERT_RETURN(m && name, NULL);
724 return pjmedia_sdp_attr_find2(m->attr_count, m->attr, name, fmt);
725}
726
727
728PJ_DEF(pj_status_t) pjmedia_sdp_media_add_attr( pjmedia_sdp_media *m,
729 pjmedia_sdp_attr *attr)
730{
731 return pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
732}
733
734PJ_DEF(pj_status_t) pjmedia_sdp_session_add_attr(pjmedia_sdp_session *s,
735 pjmedia_sdp_attr *attr)
736{
737 return pjmedia_sdp_attr_add(&s->attr_count, s->attr, attr);
738}
739
740PJ_DEF(unsigned) pjmedia_sdp_media_remove_all_attr(pjmedia_sdp_media *m,
741 const char *name)
742{
743 return pjmedia_sdp_attr_remove_all(&m->attr_count, m->attr, name);
744}
745
746PJ_DEF(pj_status_t) pjmedia_sdp_media_remove_attr(pjmedia_sdp_media *m,
747 pjmedia_sdp_attr *attr)
748{
749 return pjmedia_sdp_attr_remove(&m->attr_count, m->attr, attr);
750}
751
752static int print_session(const pjmedia_sdp_session *ses,
753 char *buf, pj_ssize_t len)
754{
755 char *p = buf;
756 char *end = buf+len;
757 unsigned i;
758 int printed;
759
760 /* Check length for v= and o= lines. */
761 if (len < 5+
762 2+ses->origin.user.slen+18+
763 ses->origin.net_type.slen+ses->origin.addr.slen + 2)
764 {
765 return -1;
766 }
767
768 /* SDP version (v= line) */
769 pj_memcpy(p, "v=0\r\n", 5);
770 p += 5;
771
772 /* Owner (o=) line. */
773 *p++ = 'o';
774 *p++ = '=';
775 pj_memcpy(p, ses->origin.user.ptr, ses->origin.user.slen);
776 p += ses->origin.user.slen;
777 *p++ = ' ';
778 printed = pj_utoa(ses->origin.id, p);
779 p += printed;
780 *p++ = ' ';
781 printed = pj_utoa(ses->origin.version, p);
782 p += printed;
783 *p++ = ' ';
784 pj_memcpy(p, ses->origin.net_type.ptr, ses->origin.net_type.slen);
785 p += ses->origin.net_type.slen;
786 *p++ = ' ';
787 pj_memcpy(p, ses->origin.addr_type.ptr, ses->origin.addr_type.slen);
788 p += ses->origin.addr_type.slen;
789 *p++ = ' ';
790 pj_memcpy(p, ses->origin.addr.ptr, ses->origin.addr.slen);
791 p += ses->origin.addr.slen;
792 *p++ = '\r';
793 *p++ = '\n';
794
795 /* Session name (s=) line. */
796 if ((end-p) < 8+ses->name.slen) {
797 return -1;
798 }
799 *p++ = 's';
800 *p++ = '=';
801 pj_memcpy(p, ses->name.ptr, ses->name.slen);
802 p += ses->name.slen;
803 *p++ = '\r';
804 *p++ = '\n';
805
806 /* Connection line (c=) if exist. */
807 if (ses->conn) {
808 printed = print_connection_info(ses->conn, p, (int)(end-p));
809 if (printed < 1) {
810 return -1;
811 }
812 p += printed;
813 }
814
815 /* print optional bandwidth info. */
816 for (i=0; i<ses->bandw_count; ++i) {
817 printed = (int)print_bandw(ses->bandw[i], p, end-p);
818 if (printed < 1) {
819 return -1;
820 }
821 p += printed;
822 }
823
824 /* Time */
825 if ((end-p) < 24) {
826 return -1;
827 }
828 *p++ = 't';
829 *p++ = '=';
830 printed = pj_utoa(ses->time.start, p);
831 p += printed;
832 *p++ = ' ';
833 printed = pj_utoa(ses->time.stop, p);
834 p += printed;
835 *p++ = '\r';
836 *p++ = '\n';
837
838 /* Print all attribute (a=) lines. */
839 for (i=0; i<ses->attr_count; ++i) {
840 printed = (int)print_attr(ses->attr[i], p, end-p);
841 if (printed < 0) {
842 return -1;
843 }
844 p += printed;
845 }
846
847 /* Print media (m=) lines. */
848 for (i=0; i<ses->media_count; ++i) {
849 printed = print_media_desc(ses->media[i], p, (int)(end-p));
850 if (printed < 0) {
851 return -1;
852 }
853 p += printed;
854 }
855
856 return (int)(p-buf);
857}
858
859/******************************************************************************
860 * PARSERS
861 */
862
863static void parse_version(pj_scanner *scanner, parse_context *ctx)
864{
865 ctx->last_error = PJMEDIA_SDP_EINVER;
866
867 /* check equal sign */
868 if (*(scanner->curptr+1) != '=') {
869 on_scanner_error(scanner);
870 return;
871 }
872
873 /* check version is 0 */
874 if (*(scanner->curptr+2) != '0') {
875 on_scanner_error(scanner);
876 return;
877 }
878
879 /* We've got what we're looking for, skip anything until newline */
880 pj_scan_skip_line(scanner);
881}
882
883static void parse_origin(pj_scanner *scanner, pjmedia_sdp_session *ses,
884 parse_context *ctx)
885{
886 pj_str_t str;
887
888 ctx->last_error = PJMEDIA_SDP_EINORIGIN;
889
890 /* check equal sign */
891 if (*(scanner->curptr+1) != '=') {
892 on_scanner_error(scanner);
893 return;
894 }
895
896 /* o= */
897 pj_scan_advance_n(scanner, 2, SKIP_WS);
898
899 /* username. */
900 pj_scan_get_until_ch(scanner, ' ', &ses->origin.user);
901 pj_scan_get_char(scanner);
902
903 /* id */
904 pj_scan_get_until_ch(scanner, ' ', &str);
905 ses->origin.id = pj_strtoul(&str);
906 pj_scan_get_char(scanner);
907
908 /* version */
909 pj_scan_get_until_ch(scanner, ' ', &str);
910 ses->origin.version = pj_strtoul(&str);
911 pj_scan_get_char(scanner);
912
913 /* network-type */
914 pj_scan_get_until_ch(scanner, ' ', &ses->origin.net_type);
915 pj_scan_get_char(scanner);
916
917 /* addr-type */
918 pj_scan_get_until_ch(scanner, ' ', &ses->origin.addr_type);
919 pj_scan_get_char(scanner);
920
921 /* address */
922 pj_scan_get_until_chr(scanner, " \t\r\n", &ses->origin.addr);
923
924 /* We've got what we're looking for, skip anything until newline */
925 pj_scan_skip_line(scanner);
926
927}
928
929static void parse_time(pj_scanner *scanner, pjmedia_sdp_session *ses,
930 parse_context *ctx)
931{
932 pj_str_t str;
933
934 ctx->last_error = PJMEDIA_SDP_EINTIME;
935
936 /* check equal sign */
937 if (*(scanner->curptr+1) != '=') {
938 on_scanner_error(scanner);
939 return;
940 }
941
942 /* t= */
943 pj_scan_advance_n(scanner, 2, SKIP_WS);
944
945 /* start time */
946 pj_scan_get_until_ch(scanner, ' ', &str);
947 ses->time.start = pj_strtoul(&str);
948
949 pj_scan_get_char(scanner);
950
951 /* stop time */
952 pj_scan_get_until_chr(scanner, " \t\r\n", &str);
953 ses->time.stop = pj_strtoul(&str);
954
955 /* We've got what we're looking for, skip anything until newline */
956 pj_scan_skip_line(scanner);
957}
958
959static void parse_generic_line(pj_scanner *scanner, pj_str_t *str,
960 parse_context *ctx)
961{
962 ctx->last_error = PJMEDIA_SDP_EINSDP;
963
964 /* check equal sign */
965 if (*(scanner->curptr+1) != '=') {
966 on_scanner_error(scanner);
967 return;
968 }
969
970 /* x= */
971 pj_scan_advance_n(scanner, 2, SKIP_WS);
972
973 /* get anything until newline (including whitespaces). */
974 pj_scan_get_until_chr(scanner, "\r\n", str);
975
976 /* newline. */
977 pj_scan_get_newline(scanner);
978}
979
980static void parse_connection_info(pj_scanner *scanner, pjmedia_sdp_conn *conn,
981 parse_context *ctx)
982{
983 ctx->last_error = PJMEDIA_SDP_EINCONN;
984
985 /* c= */
986 pj_scan_advance_n(scanner, 2, SKIP_WS);
987
988 /* network-type */
989 pj_scan_get_until_ch(scanner, ' ', &conn->net_type);
990 pj_scan_get_char(scanner);
991
992 /* addr-type */
993 pj_scan_get_until_ch(scanner, ' ', &conn->addr_type);
994 pj_scan_get_char(scanner);
995
996 /* address. */
997 pj_scan_get_until_chr(scanner, "/ \t\r\n", &conn->addr);
998 PJ_TODO(PARSE_SDP_CONN_ADDRESS_SUBFIELDS);
999
1000 /* We've got what we're looking for, skip anything until newline */
1001 pj_scan_skip_line(scanner);
1002}
1003
1004static void parse_bandwidth_info(pj_scanner *scanner, pjmedia_sdp_bandw *bandw,
1005 parse_context *ctx)
1006{
1007 pj_str_t str;
1008
1009 ctx->last_error = PJMEDIA_SDP_EINBANDW;
1010
1011 /* b= */
1012 pj_scan_advance_n(scanner, 2, SKIP_WS);
1013
1014 /* modifier */
1015 pj_scan_get_until_ch(scanner, ':', &bandw->modifier);
1016 pj_scan_get_char(scanner);
1017
1018 /* value */
1019 pj_scan_get_until_chr(scanner, " \t\r\n", &str);
1020 bandw->value = pj_strtoul(&str);
1021
1022 /* We've got what we're looking for, skip anything until newline */
1023 pj_scan_skip_line(scanner);
1024}
1025
1026static void parse_media(pj_scanner *scanner, pjmedia_sdp_media *med,
1027 parse_context *ctx)
1028{
1029 pj_str_t str;
1030
1031 ctx->last_error = PJMEDIA_SDP_EINMEDIA;
1032
1033 /* check the equal sign */
1034 if (*(scanner->curptr+1) != '=') {
1035 on_scanner_error(scanner);
1036 return;
1037 }
1038
1039 /* m= */
1040 pj_scan_advance_n(scanner, 2, SKIP_WS);
1041
1042 /* type */
1043 pj_scan_get_until_ch(scanner, ' ', &med->desc.media);
1044 pj_scan_get_char(scanner);
1045
1046 /* port */
1047 pj_scan_get(scanner, &cs_token, &str);
1048 med->desc.port = (unsigned short)pj_strtoul(&str);
1049 if (*scanner->curptr == '/') {
1050 /* port count */
1051 pj_scan_get_char(scanner);
1052 pj_scan_get(scanner, &cs_token, &str);
1053 med->desc.port_count = pj_strtoul(&str);
1054
1055 } else {
1056 med->desc.port_count = 0;
1057 }
1058
1059 if (pj_scan_get_char(scanner) != ' ') {
1060 PJ_THROW(SYNTAX_ERROR);
1061 }
1062
1063 /* transport */
1064 pj_scan_get_until_chr(scanner, " \t\r\n", &med->desc.transport);
1065
1066 /* format list */
1067 med->desc.fmt_count = 0;
1068 while (*scanner->curptr == ' ') {
1069 pj_str_t fmt;
1070
1071 pj_scan_get_char(scanner);
1072
1073 /* Check again for the end of the line */
1074 if ((*scanner->curptr == '\r') || (*scanner->curptr == '\n'))
1075 break;
1076
1077 pj_scan_get(scanner, &cs_token, &fmt);
1078 if (med->desc.fmt_count < PJMEDIA_MAX_SDP_FMT)
1079 med->desc.fmt[med->desc.fmt_count++] = fmt;
1080 else
1081 PJ_PERROR(2,(THIS_FILE, PJ_ETOOMANY,
1082 "Error adding SDP media format %.*s, "
1083 "format is ignored",
1084 (int)fmt.slen, fmt.ptr));
1085 }
1086
1087 /* We've got what we're looking for, skip anything until newline */
1088 pj_scan_skip_line(scanner);
1089}
1090
1091static void on_scanner_error(pj_scanner *scanner)
1092{
1093 PJ_UNUSED_ARG(scanner);
1094
1095 PJ_THROW(SYNTAX_ERROR);
1096}
1097
1098static pjmedia_sdp_attr *parse_attr( pj_pool_t *pool, pj_scanner *scanner,
1099 parse_context *ctx)
1100{
1101 pjmedia_sdp_attr *attr;
1102
1103 ctx->last_error = PJMEDIA_SDP_EINATTR;
1104
1105 attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr);
1106
1107 /* check equal sign */
1108 if (*(scanner->curptr+1) != '=') {
1109 on_scanner_error(scanner);
1110 return NULL;
1111 }
1112
1113 /* skip a= */
1114 pj_scan_advance_n(scanner, 2, SKIP_WS);
1115
1116 /* get attr name. */
1117 pj_scan_get(scanner, &cs_token, &attr->name);
1118
1119 if (*scanner->curptr && *scanner->curptr != '\r' &&
1120 *scanner->curptr != '\n')
1121 {
1122 /* skip ':' if present. */
1123 if (*scanner->curptr == ':')
1124 pj_scan_get_char(scanner);
1125
1126 /* get value */
1127 if (*scanner->curptr != '\r' && *scanner->curptr != '\n') {
1128 pj_scan_get_until_chr(scanner, "\r\n", &attr->value);
1129 } else {
1130 attr->value.ptr = NULL;
1131 attr->value.slen = 0;
1132 }
1133
1134 } else {
1135 attr->value.ptr = NULL;
1136 attr->value.slen = 0;
1137 }
1138
1139 /* We've got what we're looking for, skip anything until newline */
1140 pj_scan_skip_line(scanner);
1141
1142 return attr;
1143}
1144
1145
1146/*
1147 * Apply direction attribute in session to all media.
1148 */
1149static void apply_media_direction(pjmedia_sdp_session *sdp)
1150{
1151 pjmedia_sdp_attr *dir_attr = NULL;
1152 unsigned i;
1153
1154 const pj_str_t inactive = { "inactive", 8 };
1155 const pj_str_t sendonly = { "sendonly", 8 };
1156 const pj_str_t recvonly = { "recvonly", 8 };
1157 const pj_str_t sendrecv = { "sendrecv", 8 };
1158
1159 /* Find direction attribute in session, don't need to find default
1160 * direction "sendrecv".
1161 */
1162 for (i = 0; i < sdp->attr_count && !dir_attr; ++i) {
1163 if (!pj_strcmp(&sdp->attr[i]->name, &sendonly) ||
1164 !pj_strcmp(&sdp->attr[i]->name, &recvonly) ||
1165 !pj_strcmp(&sdp->attr[i]->name, &inactive))
1166 {
1167 dir_attr = sdp->attr[i];
1168 }
1169 }
1170
1171 /* Found the direction attribute */
1172 if (dir_attr) {
1173 /* Remove the direction attribute in session */
1174 pjmedia_sdp_attr_remove(&sdp->attr_count, sdp->attr, dir_attr);
1175
1176 /* Apply the direction attribute to all media, but not overriding it
1177 * if media already has direction attribute.
1178 */
1179 for (i = 0; i < sdp->media_count; ++i) {
1180 pjmedia_sdp_media *m;
1181 unsigned j;
1182
1183 /* Find direction attribute in this media */
1184 m = sdp->media[i];
1185 for (j = 0; j < m->attr_count; ++j) {
1186 if (!pj_strcmp(&m->attr[j]->name, &sendrecv) ||
1187 !pj_strcmp(&m->attr[j]->name, &sendonly) ||
1188 !pj_strcmp(&m->attr[j]->name, &recvonly) ||
1189 !pj_strcmp(&m->attr[j]->name, &inactive))
1190 {
1191 break;
1192 }
1193 }
1194
1195 /* Not found, apply direction attribute from session */
1196 if (j == m->attr_count)
1197 pjmedia_sdp_media_add_attr(m, dir_attr);
1198 }
1199 }
1200}
1201
1202
1203/*
1204 * Parse SDP message.
1205 */
1206PJ_DEF(pj_status_t) pjmedia_sdp_parse( pj_pool_t *pool,
1207 char *buf, pj_size_t len,
1208 pjmedia_sdp_session **p_sdp)
1209{
1210 pj_scanner scanner;
1211 pjmedia_sdp_session *session;
1212 pjmedia_sdp_media *media = NULL;
1213 pjmedia_sdp_attr *attr;
1214 pjmedia_sdp_conn *conn;
1215 pjmedia_sdp_bandw *bandw;
1216 pj_str_t dummy;
1217 int cur_name = 254;
1218 parse_context ctx;
1219 PJ_USE_EXCEPTION;
1220
1221 ctx.last_error = PJ_SUCCESS;
1222
1223 init_sdp_parser();
1224
1225 pj_scan_init(&scanner, buf, len, 0, &on_scanner_error);
1226 session = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session);
1227 PJ_ASSERT_RETURN(session != NULL, PJ_ENOMEM);
1228
1229 /* Ignore leading newlines */
1230 while (*scanner.curptr=='\r' || *scanner.curptr=='\n')
1231 pj_scan_get_char(&scanner);
1232
1233 PJ_TRY {
1234 while (!pj_scan_is_eof(&scanner)) {
1235 cur_name = *scanner.curptr;
1236 switch (cur_name) {
1237 case 'a':
1238 attr = parse_attr(pool, &scanner, &ctx);
1239 if (attr) {
1240 if (media) {
1241 pjmedia_sdp_media_add_attr(media, attr);
1242 } else {
1243 pjmedia_sdp_session_add_attr(session, attr);
1244 }
1245 }
1246 break;
1247 case 'o':
1248 parse_origin(&scanner, session, &ctx);
1249 break;
1250 case 's':
1251 parse_generic_line(&scanner, &session->name, &ctx);
1252 break;
1253 case 'c':
1254 conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
1255 parse_connection_info(&scanner, conn, &ctx);
1256 if (media) {
1257 media->conn = conn;
1258 } else {
1259 session->conn = conn;
1260 }
1261 break;
1262 case 't':
1263 parse_time(&scanner, session, &ctx);
1264 break;
1265 case 'm':
1266 media = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1267 parse_media(&scanner, media, &ctx);
1268 if (session->media_count < PJMEDIA_MAX_SDP_MEDIA)
1269 session->media[ session->media_count++ ] = media;
1270 else
1271 PJ_PERROR(2,(THIS_FILE, PJ_ETOOMANY,
1272 "Error adding media, media is ignored"));
1273 break;
1274 case 'v':
1275 parse_version(&scanner, &ctx);
1276 break;
1277 case 13:
1278 case 10:
1279 pj_scan_get_char(&scanner);
1280 /* Allow empty newlines at the end of the message */
1281 while (!pj_scan_is_eof(&scanner)) {
1282 if (*scanner.curptr != 13 && *scanner.curptr != 10) {
1283 ctx.last_error = PJMEDIA_SDP_EINSDP;
1284 on_scanner_error(&scanner);
1285 }
1286 pj_scan_get_char(&scanner);
1287 }
1288 break;
1289 case 'b':
1290 bandw = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_bandw);
1291 parse_bandwidth_info(&scanner, bandw, &ctx);
1292 if (media) {
1293 media->bandw[media->bandw_count++] = bandw;
1294 } else {
1295 session->bandw[session->bandw_count++] = bandw;
1296 }
1297 break;
1298 default:
1299 if (cur_name >= 'a' && cur_name <= 'z')
1300 parse_generic_line(&scanner, &dummy, &ctx);
1301 else {
1302 ctx.last_error = PJMEDIA_SDP_EINSDP;
1303 on_scanner_error(&scanner);
1304 }
1305 break;
1306 }
1307 }
1308
1309 ctx.last_error = PJ_SUCCESS;
1310
1311 }
1312 PJ_CATCH_ANY {
1313
1314 char errmsg[PJ_ERR_MSG_SIZE];
1315 pj_strerror(ctx.last_error, errmsg, sizeof(errmsg));
1316
1317 PJ_LOG(4, (THIS_FILE, "Error parsing SDP in line %d col %d: %s",
1318 scanner.line, pj_scan_get_col(&scanner),
1319 errmsg));
1320
1321 session = NULL;
1322
1323 pj_assert(ctx.last_error != PJ_SUCCESS);
1324 }
1325 PJ_END;
1326
1327 pj_scan_fini(&scanner);
1328
1329 if (session)
1330 apply_media_direction(session);
1331
1332 *p_sdp = session;
1333 return ctx.last_error;
1334}
1335
1336/*
1337 * Print SDP description.
1338 */
1339PJ_DEF(int) pjmedia_sdp_print( const pjmedia_sdp_session *desc,
1340 char *buf, pj_size_t size)
1341{
1342 return print_session(desc, buf, size);
1343}
1344
1345
1346/*
1347 * Clone session
1348 */
1349PJ_DEF(pjmedia_sdp_session*) pjmedia_sdp_session_clone( pj_pool_t *pool,
1350 const pjmedia_sdp_session *rhs)
1351{
1352 pjmedia_sdp_session *sess;
1353 unsigned i;
1354
1355 PJ_ASSERT_RETURN(pool && rhs, NULL);
1356
1357 sess = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session);
1358 PJ_ASSERT_RETURN(sess != NULL, NULL);
1359
1360 /* Clone origin line. */
1361 pj_strdup(pool, &sess->origin.user, &rhs->origin.user);
1362 sess->origin.id = rhs->origin.id;
1363 sess->origin.version = rhs->origin.version;
1364 pj_strdup(pool, &sess->origin.net_type, &rhs->origin.net_type);
1365 pj_strdup(pool, &sess->origin.addr_type, &rhs->origin.addr_type);
1366 pj_strdup(pool, &sess->origin.addr, &rhs->origin.addr);
1367
1368 /* Clone subject line. */
1369 pj_strdup(pool, &sess->name, &rhs->name);
1370
1371 /* Clone connection line */
1372 if (rhs->conn) {
1373 sess->conn = pjmedia_sdp_conn_clone(pool, rhs->conn);
1374 PJ_ASSERT_RETURN(sess->conn != NULL, NULL);
1375 }
1376
1377 /* Duplicate bandwidth info */
1378 sess->bandw_count = rhs->bandw_count;
1379 for (i=0; i<rhs->bandw_count; ++i) {
1380 sess->bandw[i] = pjmedia_sdp_bandw_clone(pool, rhs->bandw[i]);
1381 }
1382
1383 /* Clone time line. */
1384 sess->time.start = rhs->time.start;
1385 sess->time.stop = rhs->time.stop;
1386
1387 /* Duplicate session attributes. */
1388 sess->attr_count = rhs->attr_count;
1389 for (i=0; i<rhs->attr_count; ++i) {
1390 sess->attr[i] = pjmedia_sdp_attr_clone(pool, rhs->attr[i]);
1391 }
1392
1393 /* Duplicate media descriptors. */
1394 sess->media_count = rhs->media_count;
1395 for (i=0; i<rhs->media_count; ++i) {
1396 sess->media[i] = pjmedia_sdp_media_clone(pool, rhs->media[i]);
1397 }
1398
1399 return sess;
1400}
1401
1402
1403#define CHECK(exp,ret) do { \
1404 /*pj_assert(exp);*/ \
1405 if (!(exp)) \
1406 return ret; \
1407 } while (0)
1408
1409/* Validate SDP connetion info. */
1410static pj_status_t validate_sdp_conn(const pjmedia_sdp_conn *c)
1411{
1412 CHECK( c, PJ_EINVAL);
1413 CHECK( pj_strcmp2(&c->net_type, "IN")==0, PJMEDIA_SDP_EINCONN);
1414 CHECK( pj_strcmp2(&c->addr_type, "IP4")==0 ||
1415 pj_strcmp2(&c->addr_type, "IP6")==0,
1416 PJMEDIA_SDP_EINCONN);
1417 CHECK( c->addr.slen != 0, PJMEDIA_SDP_EINCONN);
1418
1419 return PJ_SUCCESS;
1420}
1421
1422
1423/* Validate SDP session descriptor. */
1424PJ_DEF(pj_status_t) pjmedia_sdp_validate(const pjmedia_sdp_session *sdp)
1425{
1426 return pjmedia_sdp_validate2(sdp, PJ_TRUE);
1427}
1428
1429
1430/* Validate SDP session descriptor. */
1431PJ_DEF(pj_status_t) pjmedia_sdp_validate2(const pjmedia_sdp_session *sdp,
1432 pj_bool_t strict)
1433{
1434 unsigned i;
1435 const pj_str_t STR_RTPMAP = { "rtpmap", 6 };
1436
1437 CHECK( sdp != NULL, PJ_EINVAL);
1438
1439 /* Validate origin line. */
1440 CHECK( sdp->origin.user.slen != 0, PJMEDIA_SDP_EINORIGIN);
1441 CHECK( pj_strcmp2(&sdp->origin.net_type, "IN")==0,
1442 PJMEDIA_SDP_EINORIGIN);
1443 CHECK( pj_strcmp2(&sdp->origin.addr_type, "IP4")==0 ||
1444 pj_strcmp2(&sdp->origin.addr_type, "IP6")==0,
1445 PJMEDIA_SDP_EINORIGIN);
1446 CHECK( sdp->origin.addr.slen != 0, PJMEDIA_SDP_EINORIGIN);
1447
1448 /* Validate subject line. */
1449 CHECK( sdp->name.slen != 0, PJMEDIA_SDP_EINNAME);
1450
1451 /* Ignore start and stop time. */
1452
1453 /* If session level connection info is present, validate it. */
1454 if (sdp->conn) {
1455 pj_status_t status = validate_sdp_conn(sdp->conn);
1456 if (status != PJ_SUCCESS)
1457 return status;
1458 }
1459
1460 /* Validate each media. */
1461 for (i=0; i<sdp->media_count; ++i) {
1462 const pjmedia_sdp_media *m = sdp->media[i];
1463 unsigned j;
1464
1465 /* Validate the m= line. */
1466 CHECK( m->desc.media.slen != 0, PJMEDIA_SDP_EINMEDIA);
1467 CHECK( m->desc.transport.slen != 0, PJMEDIA_SDP_EINMEDIA);
1468 CHECK( m->desc.fmt_count != 0 || m->desc.port==0, PJMEDIA_SDP_ENOFMT);
1469
1470 /* If media level connection info is present, validate it. */
1471 if (m->conn) {
1472 pj_status_t status = validate_sdp_conn(m->conn);
1473 if (status != PJ_SUCCESS)
1474 return status;
1475 }
1476
1477 /* If media doesn't have connection info, then connection info
1478 * must be present in the session.
1479 */
1480 if (m->conn == NULL) {
1481 if (sdp->conn == NULL)
1482 if (strict || m->desc.port != 0)
1483 return PJMEDIA_SDP_EMISSINGCONN;
1484 }
1485
1486 /* Verify payload type. */
1487 for (j=0; j<m->desc.fmt_count; ++j) {
1488
1489 /* Arrgh noo!! Payload type can be non-numeric!!
1490 * RTC based programs sends "null" for instant messaging!
1491 */
1492 if (pj_isdigit(*m->desc.fmt[j].ptr)) {
1493 unsigned pt = pj_strtoul(&m->desc.fmt[j]);
1494
1495 /* Payload type is between 0 and 127.
1496 */
1497 CHECK( pt <= 127, PJMEDIA_SDP_EINPT);
1498
1499 /* If port is not zero, then for each dynamic payload type, an
1500 * rtpmap attribute must be specified.
1501 */
1502 if (m->desc.port != 0 && pt >= 96) {
1503 const pjmedia_sdp_attr *a;
1504
1505 a = pjmedia_sdp_media_find_attr(m, &STR_RTPMAP,
1506 &m->desc.fmt[j]);
1507 CHECK( a != NULL, PJMEDIA_SDP_EMISSINGRTPMAP);
1508 }
1509 }
1510 }
1511 }
1512
1513 /* Looks good. */
1514 return PJ_SUCCESS;
1515}
1516
1517
1518PJ_DEF(pj_status_t) pjmedia_sdp_transport_cmp( const pj_str_t *t1,
1519 const pj_str_t *t2)
1520{
1521 static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 };
1522 static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 };
1523
1524 /* Exactly equal? */
1525 if (pj_stricmp(t1, t2) == 0)
1526 return PJ_SUCCESS;
1527
1528 /* Compatible? */
1529 if ((!pj_stricmp(t1, &ID_RTP_AVP) || !pj_stricmp(t1, &ID_RTP_SAVP)) &&
1530 (!pj_stricmp(t2, &ID_RTP_AVP) || !pj_stricmp(t2, &ID_RTP_SAVP)))
1531 return PJ_SUCCESS;
1532
1533 return PJMEDIA_SDP_ETPORTNOTEQUAL;
1534}
1535
1536
1537PJ_DEF(pj_status_t) pjmedia_sdp_media_deactivate(pj_pool_t *pool,
1538 pjmedia_sdp_media *m)
1539{
1540 PJ_ASSERT_RETURN(m, PJ_EINVAL);
1541 PJ_UNUSED_ARG(pool);
1542
1543 /* Set port to zero */
1544 m->desc.port = 0;
1545
1546 /* And remove attributes */
1547 m->attr_count = 0;
1548
1549 return PJ_SUCCESS;
1550}
1551
1552
1553PJ_DEF(pjmedia_sdp_media*) pjmedia_sdp_media_clone_deactivate(
1554 pj_pool_t *pool,
1555 const pjmedia_sdp_media *rhs)
1556{
1557 unsigned int i;
1558 pjmedia_sdp_media *m;
1559
1560 PJ_ASSERT_RETURN(pool && rhs, NULL);
1561
1562 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1563 pj_memcpy(m, rhs, sizeof(*m));
1564
1565 /* Clone the media line only */
1566 pj_strdup (pool, &m->desc.media, &rhs->desc.media);
1567 pj_strdup (pool, &m->desc.transport, &rhs->desc.transport);
1568 for (i=0; i<rhs->desc.fmt_count; ++i)
1569 pj_strdup(pool, &m->desc.fmt[i], &rhs->desc.fmt[i]);
1570
1571 if (rhs->conn) {
1572 m->conn = pjmedia_sdp_conn_clone (pool, rhs->conn);
1573 PJ_ASSERT_RETURN(m->conn != NULL, NULL);
1574 }
1575
1576 m->bandw_count = rhs->bandw_count;
1577 for (i=0; i < rhs->bandw_count; ++i) {
1578 m->bandw[i] = pjmedia_sdp_bandw_clone (pool, rhs->bandw[i]);
1579 PJ_ASSERT_RETURN(m->bandw[i] != NULL, NULL);
1580 }
1581
1582 /* And deactivate it */
1583 pjmedia_sdp_media_deactivate(pool, m);
1584
1585 return m;
1586}