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