blob: ebed3a365e5bc9b95a1c24564fdda5f2314fbcc4 [file] [log] [blame]
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001/* $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_neg.h>
21#include <pjmedia/sdp.h>
22#include <pjmedia/errno.h>
23#include <pj/assert.h>
24#include <pj/pool.h>
25#include <pj/string.h>
26#include <pj/ctype.h>
27#include <pj/array.h>
28
29/**
30 * This structure describes SDP media negotiator.
31 */
32struct pjmedia_sdp_neg
33{
34 pjmedia_sdp_neg_state state; /**< Negotiator state. */
35 pj_bool_t prefer_remote_codec_order;
36 pj_bool_t answer_with_multiple_codecs;
37 pj_bool_t has_remote_answer;
38 pj_bool_t answer_was_remote;
39
40 pjmedia_sdp_session *initial_sdp, /**< Initial local SDP */
41 *active_local_sdp, /**< Currently active local SDP. */
42 *active_remote_sdp, /**< Currently active remote's. */
43 *neg_local_sdp, /**< Temporary local SDP. */
44 *neg_remote_sdp; /**< Temporary remote SDP. */
45};
46
47static const char *state_str[] =
48{
49 "STATE_NULL",
50 "STATE_LOCAL_OFFER",
51 "STATE_REMOTE_OFFER",
52 "STATE_WAIT_NEGO",
53 "STATE_DONE",
54};
55
56/* Definition of customized SDP format negotiation callback */
57struct fmt_match_cb_t
58{
59 pj_str_t fmt_name;
60 pjmedia_sdp_neg_fmt_match_cb cb;
61};
62
63/* Number of registered customized SDP format negotiation callbacks */
64static unsigned fmt_match_cb_cnt;
65
66/* The registered customized SDP format negotiation callbacks */
67static struct fmt_match_cb_t
68 fmt_match_cb[PJMEDIA_SDP_NEG_MAX_CUSTOM_FMT_NEG_CB];
69
70/* Redefining a very long identifier name, just for convenience */
71#define ALLOW_MODIFY_ANSWER PJMEDIA_SDP_NEG_FMT_MATCH_ALLOW_MODIFY_ANSWER
72
73static pj_status_t custom_fmt_match( pj_pool_t *pool,
74 const pj_str_t *fmt_name,
75 pjmedia_sdp_media *offer,
76 unsigned o_fmt_idx,
77 pjmedia_sdp_media *answer,
78 unsigned a_fmt_idx,
79 unsigned option);
80
81
82/*
83 * Get string representation of negotiator state.
84 */
85PJ_DEF(const char*) pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_state state)
86{
87 if (state >=0 && state < (pjmedia_sdp_neg_state)PJ_ARRAY_SIZE(state_str))
88 return state_str[state];
89
90 return "<?UNKNOWN?>";
91}
92
93
94/*
95 * Create with local offer.
96 */
97PJ_DEF(pj_status_t) pjmedia_sdp_neg_create_w_local_offer( pj_pool_t *pool,
98 const pjmedia_sdp_session *local,
99 pjmedia_sdp_neg **p_neg)
100{
101 pjmedia_sdp_neg *neg;
102 pj_status_t status;
103
104 /* Check arguments are valid. */
105 PJ_ASSERT_RETURN(pool && local && p_neg, PJ_EINVAL);
106
107 *p_neg = NULL;
108
109 /* Validate local offer. */
110 PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(local))==PJ_SUCCESS, status);
111
112 /* Create and initialize negotiator. */
113 neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg);
114 PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM);
115
116 neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;
117 neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER;
118 neg->answer_with_multiple_codecs = PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS;
119 neg->initial_sdp = pjmedia_sdp_session_clone(pool, local);
120 neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local);
121
122 *p_neg = neg;
123 return PJ_SUCCESS;
124}
125
126/*
127 * Create with remote offer and initial local offer/answer.
128 */
129PJ_DEF(pj_status_t) pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool,
130 const pjmedia_sdp_session *initial,
131 const pjmedia_sdp_session *remote,
132 pjmedia_sdp_neg **p_neg)
133{
134 pjmedia_sdp_neg *neg;
135 pj_status_t status;
136
137 /* Check arguments are valid. */
138 PJ_ASSERT_RETURN(pool && remote && p_neg, PJ_EINVAL);
139
140 *p_neg = NULL;
141
142 /* Validate remote offer and initial answer */
143 status = pjmedia_sdp_validate2(remote, PJ_FALSE);
144 if (status != PJ_SUCCESS)
145 return status;
146
147 /* Create and initialize negotiator. */
148 neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg);
149 PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM);
150
151 neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER;
152 neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);
153
154 if (initial) {
155 PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(initial))==PJ_SUCCESS,
156 status);
157
158 neg->initial_sdp = pjmedia_sdp_session_clone(pool, initial);
159 neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, initial);
160
161 neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
162
163 } else {
164
165 neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER;
166
167 }
168
169 *p_neg = neg;
170 return PJ_SUCCESS;
171}
172
173
174/*
175 * Set codec order preference.
176 */
177PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_prefer_remote_codec_order(
178 pjmedia_sdp_neg *neg,
179 pj_bool_t prefer_remote)
180{
181 PJ_ASSERT_RETURN(neg, PJ_EINVAL);
182 neg->prefer_remote_codec_order = prefer_remote;
183 return PJ_SUCCESS;
184}
185
186
187/*
188 * Set multiple codec answering.
189 */
190PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_answer_multiple_codecs(
191 pjmedia_sdp_neg *neg,
192 pj_bool_t answer_multiple)
193{
194 PJ_ASSERT_RETURN(neg, PJ_EINVAL);
195 neg->answer_with_multiple_codecs = answer_multiple;
196 return PJ_SUCCESS;
197}
198
199
200/*
201 * Get SDP negotiator state.
202 */
203PJ_DEF(pjmedia_sdp_neg_state) pjmedia_sdp_neg_get_state( pjmedia_sdp_neg *neg )
204{
205 /* Check arguments are valid. */
206 PJ_ASSERT_RETURN(neg != NULL, PJMEDIA_SDP_NEG_STATE_NULL);
207 return neg->state;
208}
209
210
211PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_active_local( pjmedia_sdp_neg *neg,
212 const pjmedia_sdp_session **local)
213{
214 PJ_ASSERT_RETURN(neg && local, PJ_EINVAL);
215 PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE);
216
217 *local = neg->active_local_sdp;
218 return PJ_SUCCESS;
219}
220
221
222PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_active_remote( pjmedia_sdp_neg *neg,
223 const pjmedia_sdp_session **remote)
224{
225 PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL);
226 PJ_ASSERT_RETURN(neg->active_remote_sdp, PJMEDIA_SDPNEG_ENOACTIVE);
227
228 *remote = neg->active_remote_sdp;
229 return PJ_SUCCESS;
230}
231
232
233PJ_DEF(pj_bool_t) pjmedia_sdp_neg_was_answer_remote(pjmedia_sdp_neg *neg)
234{
235 PJ_ASSERT_RETURN(neg, PJ_FALSE);
236
237 return neg->answer_was_remote;
238}
239
240
241PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_remote( pjmedia_sdp_neg *neg,
242 const pjmedia_sdp_session **remote)
243{
244 PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL);
245 PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJMEDIA_SDPNEG_ENONEG);
246
247 *remote = neg->neg_remote_sdp;
248 return PJ_SUCCESS;
249}
250
251PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_local( pjmedia_sdp_neg *neg,
252 const pjmedia_sdp_session **local)
253{
254 PJ_ASSERT_RETURN(neg && local, PJ_EINVAL);
255 PJ_ASSERT_RETURN(neg->neg_local_sdp, PJMEDIA_SDPNEG_ENONEG);
256
257 *local = neg->neg_local_sdp;
258 return PJ_SUCCESS;
259}
260
261static pjmedia_sdp_media *sdp_media_clone_deactivate(
262 pj_pool_t *pool,
263 const pjmedia_sdp_media *rem_med,
264 const pjmedia_sdp_media *local_med,
265 const pjmedia_sdp_session *local_sess)
266{
267 pjmedia_sdp_media *res;
268
269 res = pjmedia_sdp_media_clone_deactivate(pool, rem_med);
270 if (!res)
271 return NULL;
272
273 if (!res->conn && (!local_sess || !local_sess->conn)) {
274 if (local_med && local_med->conn)
275 res->conn = pjmedia_sdp_conn_clone(pool, local_med->conn);
276 else {
277 res->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
278 res->conn->net_type = pj_str("IN");
279 res->conn->addr_type = pj_str("IP4");
280 res->conn->addr = pj_str("127.0.0.1");
281 }
282 }
283
284 return res;
285}
286
287/*
288 * Modify local SDP and wait for remote answer.
289 */
290PJ_DEF(pj_status_t) pjmedia_sdp_neg_modify_local_offer( pj_pool_t *pool,
291 pjmedia_sdp_neg *neg,
292 const pjmedia_sdp_session *local)
293{
294 return pjmedia_sdp_neg_modify_local_offer2(pool, neg, 0, local);
295}
296
297PJ_DEF(pj_status_t) pjmedia_sdp_neg_modify_local_offer2(
298 pj_pool_t *pool,
299 pjmedia_sdp_neg *neg,
300 unsigned flags,
301 const pjmedia_sdp_session *local)
302{
303 pjmedia_sdp_session *new_offer;
304 pjmedia_sdp_session *old_offer;
305 char media_used[PJMEDIA_MAX_SDP_MEDIA];
306 unsigned oi; /* old offer media index */
307 pj_status_t status;
308
309 /* Check arguments are valid. */
310 PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL);
311
312 /* Can only do this in STATE_DONE. */
313 PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE,
314 PJMEDIA_SDPNEG_EINSTATE);
315
316 /* Validate the new offer */
317 status = pjmedia_sdp_validate(local);
318 if (status != PJ_SUCCESS)
319 return status;
320
321 /* Change state to STATE_LOCAL_OFFER */
322 neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;
323
324 /* Init vars */
325 pj_bzero(media_used, sizeof(media_used));
326 old_offer = neg->active_local_sdp;
327 new_offer = pjmedia_sdp_session_clone(pool, local);
328
329 /* RFC 3264 Section 8: When issuing an offer that modifies the session,
330 * the "o=" line of the new SDP MUST be identical to that in the
331 * previous SDP, except that the version in the origin field MUST
332 * increment by one from the previous SDP.
333 */
334 pj_strdup(pool, &new_offer->origin.user, &old_offer->origin.user);
335 new_offer->origin.id = old_offer->origin.id;
336 new_offer->origin.version = old_offer->origin.version + 1;
337 pj_strdup(pool, &new_offer->origin.net_type, &old_offer->origin.net_type);
338 pj_strdup(pool, &new_offer->origin.addr_type,&old_offer->origin.addr_type);
339 pj_strdup(pool, &new_offer->origin.addr, &old_offer->origin.addr);
340
341 if ((flags & PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE) == 0) {
342 /* Generating the new offer, in the case media lines doesn't match the
343 * active SDP (e.g. current/active SDP's have m=audio and m=video lines,
344 * and the new offer only has m=audio line), the negotiator will fix
345 * the new offer by reordering and adding the missing media line with
346 * port number set to zero.
347 */
348 for (oi = 0; oi < old_offer->media_count; ++oi) {
349 pjmedia_sdp_media *om;
350 pjmedia_sdp_media *nm;
351 unsigned ni; /* new offer media index */
352 pj_bool_t found = PJ_FALSE;
353
354 om = old_offer->media[oi];
355 for (ni = oi; ni < new_offer->media_count; ++ni) {
356 nm = new_offer->media[ni];
357 if (pj_strcmp(&nm->desc.media, &om->desc.media) == 0) {
358 if (ni != oi) {
359 /* The same media found but the position unmatched to
360 * the old offer, so let's put this media in the right
361 * place, and keep the order of the rest.
362 */
363 pj_array_insert(
364 new_offer->media, /* array */
365 sizeof(new_offer->media[0]), /* elmt size*/
366 ni, /* count */
367 oi, /* pos */
368 &nm); /* new elmt */
369 }
370 found = PJ_TRUE;
371 break;
372 }
373 }
374 if (!found) {
375 pjmedia_sdp_media *m;
376
377 m = sdp_media_clone_deactivate(pool, om, om, local);
378
379 pj_array_insert(new_offer->media, sizeof(new_offer->media[0]),
380 new_offer->media_count++, oi, &m);
381 }
382 }
383 } else {
384 /* If media type change is allowed, the negotiator only needs to fix
385 * the new offer by adding the missing media line(s) with port number
386 * set to zero.
387 */
388 for (oi = new_offer->media_count; oi < old_offer->media_count; ++oi) {
389 pjmedia_sdp_media *m;
390
391 m = sdp_media_clone_deactivate(pool, old_offer->media[oi],
392 old_offer->media[oi], local);
393
394 pj_array_insert(new_offer->media, sizeof(new_offer->media[0]),
395 new_offer->media_count++, oi, &m);
396
397 }
398 }
399
400 /* New_offer fixed */
401 neg->initial_sdp = new_offer;
402 neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, new_offer);
403
404 return PJ_SUCCESS;
405}
406
407
408PJ_DEF(pj_status_t) pjmedia_sdp_neg_send_local_offer( pj_pool_t *pool,
409 pjmedia_sdp_neg *neg,
410 const pjmedia_sdp_session **offer)
411{
412 /* Check arguments are valid. */
413 PJ_ASSERT_RETURN(neg && offer, PJ_EINVAL);
414
415 *offer = NULL;
416
417 /* Can only do this in STATE_DONE or STATE_LOCAL_OFFER. */
418 PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE ||
419 neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER,
420 PJMEDIA_SDPNEG_EINSTATE);
421
422 if (neg->state == PJMEDIA_SDP_NEG_STATE_DONE) {
423 /* If in STATE_DONE, set the active SDP as the offer. */
424 PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE);
425
426 neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;
427 neg->neg_local_sdp = pjmedia_sdp_session_clone(pool,
428 neg->active_local_sdp);
429 *offer = neg->active_local_sdp;
430
431 } else {
432 /* We assume that we're in STATE_LOCAL_OFFER.
433 * In this case set the neg_local_sdp as the offer.
434 */
435 *offer = neg->neg_local_sdp;
436 }
437
438
439 return PJ_SUCCESS;
440}
441
442
443PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_remote_answer( pj_pool_t *pool,
444 pjmedia_sdp_neg *neg,
445 const pjmedia_sdp_session *remote)
446{
447 /* Check arguments are valid. */
448 PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL);
449
450 /* Can only do this in STATE_LOCAL_OFFER.
451 * If we haven't provided local offer, then rx_remote_offer() should
452 * be called instead of this function.
453 */
454 PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER,
455 PJMEDIA_SDPNEG_EINSTATE);
456
457 /* We're ready to negotiate. */
458 neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
459 neg->has_remote_answer = PJ_TRUE;
460 neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);
461
462 return PJ_SUCCESS;
463}
464
465PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_remote_offer( pj_pool_t *pool,
466 pjmedia_sdp_neg *neg,
467 const pjmedia_sdp_session *remote)
468{
469 /* Check arguments are valid. */
470 PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL);
471
472 /* Can only do this in STATE_DONE.
473 * If we already provide local offer, then rx_remote_answer() should
474 * be called instead of this function.
475 */
476 PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE,
477 PJMEDIA_SDPNEG_EINSTATE);
478
479 /* State now is STATE_REMOTE_OFFER. */
480 neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER;
481 neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);
482
483 return PJ_SUCCESS;
484}
485
486PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_local_answer( pj_pool_t *pool,
487 pjmedia_sdp_neg *neg,
488 const pjmedia_sdp_session *local)
489{
490 /* Check arguments are valid. */
491 PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL);
492
493 /* Can only do this in STATE_REMOTE_OFFER.
494 * If we already provide local offer, then rx_remote_answer() should
495 * be called instead of this function.
496 */
497 PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER,
498 PJMEDIA_SDPNEG_EINSTATE);
499
500 /* State now is STATE_WAIT_NEGO. */
501 neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
502 if (local) {
503 neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local);
504 if (neg->initial_sdp) {
505 /* I don't think there is anything in RFC 3264 that mandates
506 * answerer to place the same origin (and increment version)
507 * in the answer, but probably it won't hurt either.
508 * Note that the version will be incremented in
509 * pjmedia_sdp_neg_negotiate()
510 */
511 neg->neg_local_sdp->origin.id = neg->initial_sdp->origin.id;
512 } else {
513 neg->initial_sdp = pjmedia_sdp_session_clone(pool, local);
514 }
515 } else {
516 PJ_ASSERT_RETURN(neg->initial_sdp, PJMEDIA_SDPNEG_ENOINITIAL);
517 neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp);
518 }
519
520 return PJ_SUCCESS;
521}
522
523PJ_DEF(pj_bool_t) pjmedia_sdp_neg_has_local_answer(pjmedia_sdp_neg *neg)
524{
525 pj_assert(neg && neg->state==PJMEDIA_SDP_NEG_STATE_WAIT_NEGO);
526 return !neg->has_remote_answer;
527}
528
529
530/* Swap string. */
531static void str_swap(pj_str_t *str1, pj_str_t *str2)
532{
533 pj_str_t tmp = *str1;
534 *str1 = *str2;
535 *str2 = tmp;
536}
537
538static void remove_all_media_directions(pjmedia_sdp_media *m)
539{
540 pjmedia_sdp_media_remove_all_attr(m, "inactive");
541 pjmedia_sdp_media_remove_all_attr(m, "sendrecv");
542 pjmedia_sdp_media_remove_all_attr(m, "sendonly");
543 pjmedia_sdp_media_remove_all_attr(m, "recvonly");
544}
545
546/* Update media direction based on peer's media direction */
547static void update_media_direction(pj_pool_t *pool,
548 const pjmedia_sdp_media *remote,
549 pjmedia_sdp_media *local)
550{
551 pjmedia_dir old_dir = PJMEDIA_DIR_ENCODING_DECODING,
552 new_dir;
553
554 /* Get the media direction of local SDP */
555 if (pjmedia_sdp_media_find_attr2(local, "sendonly", NULL))
556 old_dir = PJMEDIA_DIR_ENCODING;
557 else if (pjmedia_sdp_media_find_attr2(local, "recvonly", NULL))
558 old_dir = PJMEDIA_DIR_DECODING;
559 else if (pjmedia_sdp_media_find_attr2(local, "inactive", NULL))
560 old_dir = PJMEDIA_DIR_NONE;
561
562 new_dir = old_dir;
563
564 /* Adjust local media direction based on remote media direction */
565 if (pjmedia_sdp_media_find_attr2(remote, "inactive", NULL) != NULL) {
566 /* If remote has "a=inactive", then local is inactive too */
567
568 new_dir = PJMEDIA_DIR_NONE;
569
570 } else if(pjmedia_sdp_media_find_attr2(remote, "sendonly", NULL) != NULL) {
571 /* If remote has "a=sendonly", then set local to "recvonly" if
572 * it is currently "sendrecv". Otherwise if local is NOT "recvonly",
573 * then set local direction to "inactive".
574 */
575 switch (old_dir) {
576 case PJMEDIA_DIR_ENCODING_DECODING:
577 new_dir = PJMEDIA_DIR_DECODING;
578 break;
579 case PJMEDIA_DIR_DECODING:
580 /* No change */
581 break;
582 default:
583 new_dir = PJMEDIA_DIR_NONE;
584 break;
585 }
586
587 } else if(pjmedia_sdp_media_find_attr2(remote, "recvonly", NULL) != NULL) {
588 /* If remote has "a=recvonly", then set local to "sendonly" if
589 * it is currently "sendrecv". Otherwise if local is NOT "sendonly",
590 * then set local direction to "inactive"
591 */
592
593 switch (old_dir) {
594 case PJMEDIA_DIR_ENCODING_DECODING:
595 new_dir = PJMEDIA_DIR_ENCODING;
596 break;
597 case PJMEDIA_DIR_ENCODING:
598 /* No change */
599 break;
600 default:
601 new_dir = PJMEDIA_DIR_NONE;
602 break;
603 }
604
605 } else {
606 /* Remote indicates "sendrecv" capability. No change to local
607 * direction
608 */
609 }
610
611 if (new_dir != old_dir) {
612 pjmedia_sdp_attr *a = NULL;
613
614 remove_all_media_directions(local);
615
616 switch (new_dir) {
617 case PJMEDIA_DIR_NONE:
618 a = pjmedia_sdp_attr_create(pool, "inactive", NULL);
619 break;
620 case PJMEDIA_DIR_ENCODING:
621 a = pjmedia_sdp_attr_create(pool, "sendonly", NULL);
622 break;
623 case PJMEDIA_DIR_DECODING:
624 a = pjmedia_sdp_attr_create(pool, "recvonly", NULL);
625 break;
626 default:
627 /* sendrecv */
628 break;
629 }
630
631 if (a) {
632 pjmedia_sdp_media_add_attr(local, a);
633 }
634 }
635}
636
637
638/* Update single local media description to after receiving answer
639 * from remote.
640 */
641static pj_status_t process_m_answer( pj_pool_t *pool,
642 pjmedia_sdp_media *offer,
643 pjmedia_sdp_media *answer,
644 pj_bool_t allow_asym)
645{
646 unsigned i;
647
648 /* Check that the media type match our offer. */
649
650 if (pj_strcmp(&answer->desc.media, &offer->desc.media)!=0) {
651 /* The media type in the answer is different than the offer! */
652 return PJMEDIA_SDPNEG_EINVANSMEDIA;
653 }
654
655
656 /* Check that transport in the answer match our offer. */
657
658 /* At this point, transport type must be compatible,
659 * the transport instance will do more validation later.
660 */
661 if (pjmedia_sdp_transport_cmp(&answer->desc.transport,
662 &offer->desc.transport)
663 != PJ_SUCCESS)
664 {
665 return PJMEDIA_SDPNEG_EINVANSTP;
666 }
667
668
669 /* Check if remote has rejected our offer */
670 if (answer->desc.port == 0) {
671
672 /* Remote has rejected our offer.
673 * Deactivate our media too.
674 */
675 pjmedia_sdp_media_deactivate(pool, offer);
676
677 /* Don't need to proceed */
678 return PJ_SUCCESS;
679 }
680
681 /* Ticket #1148: check if remote answer does not set port to zero when
682 * offered with port zero. Let's just tolerate it.
683 */
684 if (offer->desc.port == 0) {
685 /* Don't need to proceed */
686 return PJ_SUCCESS;
687 }
688
689 /* Process direction attributes */
690 update_media_direction(pool, answer, offer);
691
692 /* If asymetric media is allowed, then just check that remote answer has
693 * codecs that are within the offer.
694 *
695 * Otherwise if asymetric media is not allowed, then we will choose only
696 * one codec in our initial offer to match the answer.
697 */
698 if (allow_asym) {
699 for (i=0; i<answer->desc.fmt_count; ++i) {
700 unsigned j;
701 pj_str_t *rem_fmt = &answer->desc.fmt[i];
702
703 for (j=0; j<offer->desc.fmt_count; ++j) {
704 if (pj_strcmp(rem_fmt, &answer->desc.fmt[j])==0)
705 break;
706 }
707
708 if (j != offer->desc.fmt_count) {
709 /* Found at least one common codec. */
710 break;
711 }
712 }
713
714 if (i == answer->desc.fmt_count) {
715 /* No common codec in the answer! */
716 return PJMEDIA_SDPNEG_EANSNOMEDIA;
717 }
718
719 PJ_TODO(CHECK_SDP_NEGOTIATION_WHEN_ASYMETRIC_MEDIA_IS_ALLOWED);
720
721 } else {
722 /* Offer format priority based on answer format index/priority */
723 unsigned offer_fmt_prior[PJMEDIA_MAX_SDP_FMT];
724
725 /* Remove all format in the offer that has no matching answer */
726 for (i=0; i<offer->desc.fmt_count;) {
727 unsigned pt;
728 pj_uint32_t j;
729 pj_str_t *fmt = &offer->desc.fmt[i];
730
731
732 /* Find matching answer */
733 pt = pj_strtoul(fmt);
734
735 if (pt < 96) {
736 for (j=0; j<answer->desc.fmt_count; ++j) {
737 if (pj_strcmp(fmt, &answer->desc.fmt[j])==0)
738 break;
739 }
740 } else {
741 /* This is dynamic payload type.
742 * For dynamic payload type, we must look the rtpmap and
743 * compare the encoding name.
744 */
745 const pjmedia_sdp_attr *a;
746 pjmedia_sdp_rtpmap or_;
747
748 /* Get the rtpmap for the payload type in the offer. */
749 a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt);
750 if (!a) {
751 pj_assert(!"Bug! Offer should have been validated");
752 return PJ_EBUG;
753 }
754 pjmedia_sdp_attr_get_rtpmap(a, &or_);
755
756 /* Find paylaod in answer SDP with matching
757 * encoding name and clock rate.
758 */
759 for (j=0; j<answer->desc.fmt_count; ++j) {
760 a = pjmedia_sdp_media_find_attr2(answer, "rtpmap",
761 &answer->desc.fmt[j]);
762 if (a) {
763 pjmedia_sdp_rtpmap ar;
764 pjmedia_sdp_attr_get_rtpmap(a, &ar);
765
766 /* See if encoding name, clock rate, and channel
767 * count match
768 */
769 if (!pj_stricmp(&or_.enc_name, &ar.enc_name) &&
770 or_.clock_rate == ar.clock_rate &&
771 (pj_stricmp(&or_.param, &ar.param)==0 ||
772 (ar.param.slen==1 && *ar.param.ptr=='1')))
773 {
774 /* Call custom format matching callbacks */
775 if (custom_fmt_match(pool, &or_.enc_name,
776 offer, i, answer, j, 0) ==
777 PJ_SUCCESS)
778 {
779 /* Match! */
780 break;
781 }
782 }
783 }
784 }
785 }
786
787 if (j == answer->desc.fmt_count) {
788 /* This format has no matching answer.
789 * Remove it from our offer.
790 */
791 pjmedia_sdp_attr *a;
792
793 /* Remove rtpmap associated with this format */
794 a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt);
795 if (a)
796 pjmedia_sdp_media_remove_attr(offer, a);
797
798 /* Remove fmtp associated with this format */
799 a = pjmedia_sdp_media_find_attr2(offer, "fmtp", fmt);
800 if (a)
801 pjmedia_sdp_media_remove_attr(offer, a);
802
803 /* Remove this format from offer's array */
804 pj_array_erase(offer->desc.fmt, sizeof(offer->desc.fmt[0]),
805 offer->desc.fmt_count, i);
806 --offer->desc.fmt_count;
807
808 } else {
809 offer_fmt_prior[i] = j;
810 ++i;
811 }
812 }
813
814 if (0 == offer->desc.fmt_count) {
815 /* No common codec in the answer! */
816 return PJMEDIA_SDPNEG_EANSNOMEDIA;
817 }
818
819 /* Post process:
820 * - Resort offer formats so the order match to the answer.
821 * - Remove answer formats that unmatches to the offer.
822 */
823
824 /* Resort offer formats */
825 for (i=0; i<offer->desc.fmt_count; ++i) {
826 unsigned j;
827 for (j=i+1; j<offer->desc.fmt_count; ++j) {
828 if (offer_fmt_prior[i] > offer_fmt_prior[j]) {
829 unsigned tmp = offer_fmt_prior[i];
830 offer_fmt_prior[i] = offer_fmt_prior[j];
831 offer_fmt_prior[j] = tmp;
832 str_swap(&offer->desc.fmt[i], &offer->desc.fmt[j]);
833 }
834 }
835 }
836
837 /* Remove unmatched answer formats */
838 {
839 unsigned del_cnt = 0;
840 for (i=0; i<answer->desc.fmt_count;) {
841 /* The offer is ordered now, also the offer_fmt_prior */
842 if (i >= offer->desc.fmt_count ||
843 offer_fmt_prior[i]-del_cnt != i)
844 {
845 pj_str_t *fmt = &answer->desc.fmt[i];
846 pjmedia_sdp_attr *a;
847
848 /* Remove rtpmap associated with this format */
849 a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", fmt);
850 if (a)
851 pjmedia_sdp_media_remove_attr(answer, a);
852
853 /* Remove fmtp associated with this format */
854 a = pjmedia_sdp_media_find_attr2(answer, "fmtp", fmt);
855 if (a)
856 pjmedia_sdp_media_remove_attr(answer, a);
857
858 /* Remove this format from answer's array */
859 pj_array_erase(answer->desc.fmt,
860 sizeof(answer->desc.fmt[0]),
861 answer->desc.fmt_count, i);
862 --answer->desc.fmt_count;
863
864 ++del_cnt;
865 } else {
866 ++i;
867 }
868 }
869 }
870 }
871
872 /* Looks okay */
873 return PJ_SUCCESS;
874}
875
876
877/* Update local media session (offer) to create active local session
878 * after receiving remote answer.
879 */
880static pj_status_t process_answer(pj_pool_t *pool,
881 pjmedia_sdp_session *offer,
882 pjmedia_sdp_session *answer,
883 pj_bool_t allow_asym,
884 pjmedia_sdp_session **p_active)
885{
886 unsigned omi = 0; /* Offer media index */
887 unsigned ami = 0; /* Answer media index */
888 pj_bool_t has_active = PJ_FALSE;
889 pj_status_t status;
890
891 /* Check arguments. */
892 PJ_ASSERT_RETURN(pool && offer && answer && p_active, PJ_EINVAL);
893
894 /* Check that media count match between offer and answer */
895 // Ticket #527, different media count is allowed for more interoperability,
896 // however, the media order must be same between offer and answer.
897 // if (offer->media_count != answer->media_count)
898 // return PJMEDIA_SDPNEG_EMISMEDIA;
899
900 /* Now update each media line in the offer with the answer. */
901 for (; omi<offer->media_count; ++omi) {
902 if (ami == answer->media_count) {
903 /* The answer has less media than the offer */
904 pjmedia_sdp_media *am;
905
906 /* Generate matching-but-disabled-media for the answer */
907 am = sdp_media_clone_deactivate(pool, offer->media[omi],
908 offer->media[omi], offer);
909 answer->media[answer->media_count++] = am;
910 ++ami;
911
912 /* Deactivate our media offer too */
913 pjmedia_sdp_media_deactivate(pool, offer->media[omi]);
914
915 /* No answer media to be negotiated */
916 continue;
917 }
918
919 status = process_m_answer(pool, offer->media[omi], answer->media[ami],
920 allow_asym);
921
922 /* If media type is mismatched, just disable the media. */
923 if (status == PJMEDIA_SDPNEG_EINVANSMEDIA) {
924 pjmedia_sdp_media_deactivate(pool, offer->media[omi]);
925 continue;
926 }
927 /* No common format in the answer media. */
928 else if (status == PJMEDIA_SDPNEG_EANSNOMEDIA) {
929 pjmedia_sdp_media_deactivate(pool, offer->media[omi]);
930 pjmedia_sdp_media_deactivate(pool, answer->media[ami]);
931 }
932 /* Return the error code, for other errors. */
933 else if (status != PJ_SUCCESS) {
934 return status;
935 }
936
937 if (offer->media[omi]->desc.port != 0)
938 has_active = PJ_TRUE;
939
940 ++ami;
941 }
942
943 *p_active = offer;
944
945 return has_active ? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA;
946}
947
948
949/* Internal function to rewrite the format string in SDP attribute rtpmap
950 * and fmtp.
951 */
952PJ_INLINE(void) rewrite_pt(pj_pool_t *pool, pj_str_t *attr_val,
953 const pj_str_t *old_pt, const pj_str_t *new_pt)
954{
955 int len_diff = (int)(new_pt->slen - old_pt->slen);
956
957 /* Note that attribute value should be null-terminated. */
958 if (len_diff > 0) {
959 pj_str_t new_val;
960 new_val.ptr = (char*)pj_pool_alloc(pool, attr_val->slen+len_diff+1);
961 new_val.slen = attr_val->slen + len_diff;
962 pj_memcpy(new_val.ptr + len_diff, attr_val->ptr, attr_val->slen + 1);
963 *attr_val = new_val;
964 } else if (len_diff < 0) {
965 attr_val->slen += len_diff;
966 pj_memmove(attr_val->ptr, attr_val->ptr - len_diff,
967 attr_val->slen + 1);
968 }
969 pj_memcpy(attr_val->ptr, new_pt->ptr, new_pt->slen);
970}
971
972
973/* Internal function to apply symmetric PT for the local answer. */
974static void apply_answer_symmetric_pt(pj_pool_t *pool,
975 pjmedia_sdp_media *answer,
976 unsigned pt_cnt,
977 const pj_str_t pt_offer[],
978 const pj_str_t pt_answer[])
979{
980 pjmedia_sdp_attr *a_tmp[PJMEDIA_MAX_SDP_ATTR];
981 unsigned i, a_tmp_cnt = 0;
982
983 /* Rewrite the payload types in the answer if different to
984 * the ones in the offer.
985 */
986 for (i = 0; i < pt_cnt; ++i) {
987 pjmedia_sdp_attr *a;
988
989 /* Skip if the PTs are the same already, e.g: static PT. */
990 if (pj_strcmp(&pt_answer[i], &pt_offer[i]) == 0)
991 continue;
992
993 /* Rewrite payload type in the answer to match to the offer */
994 pj_strdup(pool, &answer->desc.fmt[i], &pt_offer[i]);
995
996 /* Also update payload type in rtpmap */
997 a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &pt_answer[i]);
998 if (a) {
999 rewrite_pt(pool, &a->value, &pt_answer[i], &pt_offer[i]);
1000 /* Temporarily remove the attribute in case the new payload
1001 * type is being used by another format in the media.
1002 */
1003 pjmedia_sdp_media_remove_attr(answer, a);
1004 a_tmp[a_tmp_cnt++] = a;
1005 }
1006
1007 /* Also update payload type in fmtp */
1008 a = pjmedia_sdp_media_find_attr2(answer, "fmtp", &pt_answer[i]);
1009 if (a) {
1010 rewrite_pt(pool, &a->value, &pt_answer[i], &pt_offer[i]);
1011 /* Temporarily remove the attribute in case the new payload
1012 * type is being used by another format in the media.
1013 */
1014 pjmedia_sdp_media_remove_attr(answer, a);
1015 a_tmp[a_tmp_cnt++] = a;
1016 }
1017 }
1018
1019 /* Return back 'rtpmap' and 'fmtp' attributes */
1020 for (i = 0; i < a_tmp_cnt; ++i)
1021 pjmedia_sdp_media_add_attr(answer, a_tmp[i]);
1022}
1023
1024
1025/* Try to match offer with answer. */
1026static pj_status_t match_offer(pj_pool_t *pool,
1027 pj_bool_t prefer_remote_codec_order,
1028 pj_bool_t answer_with_multiple_codecs,
1029 const pjmedia_sdp_media *offer,
1030 const pjmedia_sdp_media *preanswer,
1031 const pjmedia_sdp_session *preanswer_sdp,
1032 pjmedia_sdp_media **p_answer)
1033{
1034 unsigned i;
1035 pj_bool_t master_has_codec = 0,
1036 master_has_telephone_event = 0,
1037 master_has_other = 0,
1038 found_matching_codec = 0,
1039 found_matching_telephone_event = 0,
1040 found_matching_other = 0;
1041 unsigned pt_answer_count = 0;
1042 pj_str_t pt_answer[PJMEDIA_MAX_SDP_FMT];
1043 pj_str_t pt_offer[PJMEDIA_MAX_SDP_FMT];
1044 pjmedia_sdp_media *answer;
1045 const pjmedia_sdp_media *master, *slave;
1046
1047 /* If offer has zero port, just clone the offer */
1048 if (offer->desc.port == 0) {
1049 answer = sdp_media_clone_deactivate(pool, offer, preanswer,
1050 preanswer_sdp);
1051 *p_answer = answer;
1052 return PJ_SUCCESS;
1053 }
1054
1055 /* If the preanswer define zero port, this media is being rejected,
1056 * just clone the preanswer.
1057 */
1058 if (preanswer->desc.port == 0) {
1059 answer = pjmedia_sdp_media_clone(pool, preanswer);
1060 *p_answer = answer;
1061 return PJ_SUCCESS;
1062 }
1063
1064 /* Set master/slave negotiator based on prefer_remote_codec_order. */
1065 if (prefer_remote_codec_order) {
1066 master = offer;
1067 slave = preanswer;
1068 } else {
1069 master = preanswer;
1070 slave = offer;
1071 }
1072
1073 /* With the addition of telephone-event and dodgy MS RTC SDP,
1074 * the answer generation algorithm looks really shitty...
1075 */
1076 for (i=0; i<master->desc.fmt_count; ++i) {
1077 unsigned j;
1078
1079 if (pj_isdigit(*master->desc.fmt[i].ptr)) {
1080 /* This is normal/standard payload type, where it's identified
1081 * by payload number.
1082 */
1083 unsigned pt;
1084
1085 pt = pj_strtoul(&master->desc.fmt[i]);
1086
1087 if (pt < 96) {
1088 /* For static payload type, it's enough to compare just
1089 * the payload number.
1090 */
1091
1092 master_has_codec = 1;
1093
1094 /* We just need to select one codec if not allowing multiple.
1095 * Continue if we have selected matching codec for previous
1096 * payload.
1097 */
1098 if (!answer_with_multiple_codecs && found_matching_codec)
1099 continue;
1100
1101 /* Find matching codec in local descriptor. */
1102 for (j=0; j<slave->desc.fmt_count; ++j) {
1103 unsigned p;
1104 p = pj_strtoul(&slave->desc.fmt[j]);
1105 if (p == pt && pj_isdigit(*slave->desc.fmt[j].ptr)) {
1106 found_matching_codec = 1;
1107 pt_offer[pt_answer_count] = slave->desc.fmt[j];
1108 pt_answer[pt_answer_count++] = slave->desc.fmt[j];
1109 break;
1110 }
1111 }
1112
1113 } else {
1114 /* This is dynamic payload type.
1115 * For dynamic payload type, we must look the rtpmap and
1116 * compare the encoding name.
1117 */
1118 const pjmedia_sdp_attr *a;
1119 pjmedia_sdp_rtpmap or_;
1120 pj_bool_t is_codec;
1121
1122 /* Get the rtpmap for the payload type in the master. */
1123 a = pjmedia_sdp_media_find_attr2(master, "rtpmap",
1124 &master->desc.fmt[i]);
1125 if (!a) {
1126 pj_assert(!"Bug! Offer should have been validated");
1127 return PJMEDIA_SDP_EMISSINGRTPMAP;
1128 }
1129 pjmedia_sdp_attr_get_rtpmap(a, &or_);
1130
1131 if (!pj_stricmp2(&or_.enc_name, "telephone-event")) {
1132 master_has_telephone_event = 1;
1133 if (found_matching_telephone_event)
1134 continue;
1135 is_codec = 0;
1136 } else {
1137 master_has_codec = 1;
1138 if (!answer_with_multiple_codecs && found_matching_codec)
1139 continue;
1140 is_codec = 1;
1141 }
1142
1143 /* Find paylaod in our initial SDP with matching
1144 * encoding name and clock rate.
1145 */
1146 for (j=0; j<slave->desc.fmt_count; ++j) {
1147 a = pjmedia_sdp_media_find_attr2(slave, "rtpmap",
1148 &slave->desc.fmt[j]);
1149 if (a) {
1150 pjmedia_sdp_rtpmap lr;
1151 pjmedia_sdp_attr_get_rtpmap(a, &lr);
1152
1153 /* See if encoding name, clock rate, and
1154 * channel count match
1155 */
1156 if (!pj_stricmp(&or_.enc_name, &lr.enc_name) &&
1157 or_.clock_rate == lr.clock_rate &&
1158 (pj_stricmp(&or_.param, &lr.param)==0 ||
1159 (lr.param.slen==0 && or_.param.slen==1 &&
1160 *or_.param.ptr=='1') ||
1161 (or_.param.slen==0 && lr.param.slen==1 &&
1162 *lr.param.ptr=='1')))
1163 {
1164 /* Match! */
1165 if (is_codec) {
1166 pjmedia_sdp_media *o, *a;
1167 unsigned o_fmt_idx, a_fmt_idx;
1168
1169 o = (pjmedia_sdp_media*)offer;
1170 a = (pjmedia_sdp_media*)preanswer;
1171 o_fmt_idx = prefer_remote_codec_order? i:j;
1172 a_fmt_idx = prefer_remote_codec_order? j:i;
1173
1174 /* Call custom format matching callbacks */
1175 if (custom_fmt_match(pool, &or_.enc_name,
1176 o, o_fmt_idx,
1177 a, a_fmt_idx,
1178 ALLOW_MODIFY_ANSWER) !=
1179 PJ_SUCCESS)
1180 {
1181 continue;
1182 }
1183 found_matching_codec = 1;
1184 } else {
1185 found_matching_telephone_event = 1;
1186 }
1187
1188 pt_offer[pt_answer_count] =
1189 prefer_remote_codec_order?
1190 offer->desc.fmt[i]:
1191 offer->desc.fmt[j];
1192 pt_answer[pt_answer_count++] =
1193 prefer_remote_codec_order?
1194 preanswer->desc.fmt[j]:
1195 preanswer->desc.fmt[i];
1196 break;
1197 }
1198 }
1199 }
1200 }
1201
1202 } else {
1203 /* This is a non-standard, brain damaged SDP where the payload
1204 * type is non-numeric. It exists e.g. in Microsoft RTC based
1205 * UA, to indicate instant messaging capability.
1206 * Example:
1207 * - m=x-ms-message 5060 sip null
1208 */
1209 master_has_other = 1;
1210 if (found_matching_other)
1211 continue;
1212
1213 for (j=0; j<slave->desc.fmt_count; ++j) {
1214 if (!pj_strcmp(&master->desc.fmt[i], &slave->desc.fmt[j])) {
1215 /* Match */
1216 found_matching_other = 1;
1217 pt_offer[pt_answer_count] = prefer_remote_codec_order?
1218 offer->desc.fmt[i]:
1219 offer->desc.fmt[j];
1220 pt_answer[pt_answer_count++] = prefer_remote_codec_order?
1221 preanswer->desc.fmt[j]:
1222 preanswer->desc.fmt[i];
1223 break;
1224 }
1225 }
1226 }
1227 }
1228
1229 /* See if all types of master can be matched. */
1230 if (master_has_codec && !found_matching_codec) {
1231 return PJMEDIA_SDPNEG_NOANSCODEC;
1232 }
1233
1234 /* If this comment is removed, negotiation will fail if remote has offered
1235 telephone-event and local is not configured with telephone-event
1236
1237 if (offer_has_telephone_event && !found_matching_telephone_event) {
1238 return PJMEDIA_SDPNEG_NOANSTELEVENT;
1239 }
1240 */
1241
1242 if (master_has_other && !found_matching_other) {
1243 return PJMEDIA_SDPNEG_NOANSUNKNOWN;
1244 }
1245
1246 /* Seems like everything is in order.
1247 * Build the answer by cloning from preanswer, but rearrange the payload
1248 * to suit the offer.
1249 */
1250 answer = pjmedia_sdp_media_clone(pool, preanswer);
1251 for (i=0; i<pt_answer_count; ++i) {
1252 unsigned j;
1253 for (j=i; j<answer->desc.fmt_count; ++j) {
1254 if (!pj_strcmp(&answer->desc.fmt[j], &pt_answer[i]))
1255 break;
1256 }
1257 pj_assert(j != answer->desc.fmt_count);
1258 str_swap(&answer->desc.fmt[i], &answer->desc.fmt[j]);
1259 }
1260
1261 /* Remove unwanted local formats. */
1262 for (i=pt_answer_count; i<answer->desc.fmt_count; ++i) {
1263 pjmedia_sdp_attr *a;
1264
1265 /* Remove rtpmap for this format */
1266 a = pjmedia_sdp_media_find_attr2(answer, "rtpmap",
1267 &answer->desc.fmt[i]);
1268 if (a) {
1269 pjmedia_sdp_media_remove_attr(answer, a);
1270 }
1271
1272 /* Remove fmtp for this format */
1273 a = pjmedia_sdp_media_find_attr2(answer, "fmtp",
1274 &answer->desc.fmt[i]);
1275 if (a) {
1276 pjmedia_sdp_media_remove_attr(answer, a);
1277 }
1278 }
1279 answer->desc.fmt_count = pt_answer_count;
1280
1281#if PJMEDIA_SDP_NEG_ANSWER_SYMMETRIC_PT
1282 apply_answer_symmetric_pt(pool, answer, pt_answer_count,
1283 pt_offer, pt_answer);
1284#endif
1285
1286 /* Update media direction. */
1287 update_media_direction(pool, offer, answer);
1288
1289 *p_answer = answer;
1290 return PJ_SUCCESS;
1291}
1292
1293/* Create complete answer for remote's offer. */
1294static pj_status_t create_answer( pj_pool_t *pool,
1295 pj_bool_t prefer_remote_codec_order,
1296 pj_bool_t answer_with_multiple_codecs,
1297 const pjmedia_sdp_session *initial,
1298 const pjmedia_sdp_session *offer,
1299 pjmedia_sdp_session **p_answer)
1300{
1301 pj_status_t status = PJMEDIA_SDPNEG_ENOMEDIA;
1302 pj_bool_t has_active = PJ_FALSE;
1303 pjmedia_sdp_session *answer;
1304 char media_used[PJMEDIA_MAX_SDP_MEDIA];
1305 unsigned i;
1306
1307 /* Validate remote offer.
1308 * This should have been validated before.
1309 */
1310 PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(offer))==PJ_SUCCESS, status);
1311
1312 /* Create initial answer by duplicating initial SDP,
1313 * but clear all media lines. The media lines will be filled up later.
1314 */
1315 answer = pjmedia_sdp_session_clone(pool, initial);
1316 PJ_ASSERT_RETURN(answer != NULL, PJ_ENOMEM);
1317
1318 answer->media_count = 0;
1319
1320 pj_bzero(media_used, sizeof(media_used));
1321
1322 /* For each media line, create our answer based on our initial
1323 * capability.
1324 */
1325 for (i=0; i<offer->media_count; ++i) {
1326 const pjmedia_sdp_media *om; /* offer */
1327 const pjmedia_sdp_media *im; /* initial media */
1328 pjmedia_sdp_media *am = NULL; /* answer/result */
1329 unsigned j;
1330
1331 om = offer->media[i];
1332
1333 /* Find media description in our initial capability that matches
1334 * the media type and transport type of offer's media, has
1335 * matching codec, and has not been used to answer other offer.
1336 */
1337 for (im=NULL, j=0; j<initial->media_count; ++j) {
1338 im = initial->media[j];
1339 if (pj_strcmp(&om->desc.media, &im->desc.media)==0 &&
1340 pj_strcmp(&om->desc.transport, &im->desc.transport)==0 &&
1341 media_used[j] == 0)
1342 {
1343 pj_status_t status2;
1344
1345 /* See if it has matching codec. */
1346 status2 = match_offer(pool, prefer_remote_codec_order,
1347 answer_with_multiple_codecs,
1348 om, im, initial, &am);
1349 if (status2 == PJ_SUCCESS) {
1350 /* Mark media as used. */
1351 media_used[j] = 1;
1352 break;
1353 } else {
1354 status = status2;
1355 }
1356 }
1357 }
1358
1359 if (j==initial->media_count) {
1360 /* No matching media.
1361 * Reject the offer by setting the port to zero in the answer.
1362 */
1363 /* For simplicity in the construction of the answer, we'll
1364 * just clone the media from the offer. Anyway receiver will
1365 * ignore anything in the media once it sees that the port
1366 * number is zero.
1367 */
1368 am = sdp_media_clone_deactivate(pool, om, om, answer);
1369 } else {
1370 /* The answer is in am */
1371 pj_assert(am != NULL);
1372 }
1373
1374 /* Add the media answer */
1375 answer->media[answer->media_count++] = am;
1376
1377 /* Check if this media is active.*/
1378 if (am->desc.port != 0)
1379 has_active = PJ_TRUE;
1380 }
1381
1382 *p_answer = answer;
1383
1384 return has_active ? PJ_SUCCESS : status;
1385}
1386
1387/* Cancel offer */
1388PJ_DEF(pj_status_t) pjmedia_sdp_neg_cancel_offer(pjmedia_sdp_neg *neg)
1389{
1390 PJ_ASSERT_RETURN(neg, PJ_EINVAL);
1391
1392 /* Must be in LOCAL_OFFER state. */
1393 PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER ||
1394 neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER,
1395 PJMEDIA_SDPNEG_EINSTATE);
1396
1397 /* Clear temporary SDP */
1398 neg->neg_local_sdp = neg->neg_remote_sdp = NULL;
1399 neg->has_remote_answer = PJ_FALSE;
1400
1401 if (neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) {
1402 /* Increment next version number. This happens if for example
1403 * the reinvite offer is rejected by 488. If we don't increment
1404 * the version here, the next offer will have the same version.
1405 */
1406 neg->active_local_sdp->origin.version++;
1407 }
1408
1409 /* Reset state to done */
1410 neg->state = PJMEDIA_SDP_NEG_STATE_DONE;
1411
1412 return PJ_SUCCESS;
1413}
1414
1415
1416/* The best bit: SDP negotiation function! */
1417PJ_DEF(pj_status_t) pjmedia_sdp_neg_negotiate( pj_pool_t *pool,
1418 pjmedia_sdp_neg *neg,
1419 pj_bool_t allow_asym)
1420{
1421 pj_status_t status;
1422
1423 /* Check arguments are valid. */
1424 PJ_ASSERT_RETURN(pool && neg, PJ_EINVAL);
1425
1426 /* Must be in STATE_WAIT_NEGO state. */
1427 PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO,
1428 PJMEDIA_SDPNEG_EINSTATE);
1429
1430 /* Must have remote offer. */
1431 PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJ_EBUG);
1432
1433 if (neg->has_remote_answer) {
1434 pjmedia_sdp_session *active;
1435 status = process_answer(pool, neg->neg_local_sdp, neg->neg_remote_sdp,
1436 allow_asym, &active);
1437 if (status == PJ_SUCCESS) {
1438 /* Only update active SDPs when negotiation is successfull */
1439 neg->active_local_sdp = active;
1440 neg->active_remote_sdp = neg->neg_remote_sdp;
1441 }
1442 } else {
1443 pjmedia_sdp_session *answer = NULL;
1444
1445 status = create_answer(pool, neg->prefer_remote_codec_order,
1446 neg->answer_with_multiple_codecs,
1447 neg->neg_local_sdp, neg->neg_remote_sdp,
1448 &answer);
1449 if (status == PJ_SUCCESS) {
1450 pj_uint32_t active_ver;
1451
1452 if (neg->active_local_sdp)
1453 active_ver = neg->active_local_sdp->origin.version;
1454 else
1455 active_ver = neg->initial_sdp->origin.version;
1456
1457 /* Only update active SDPs when negotiation is successfull */
1458 neg->active_local_sdp = answer;
1459 neg->active_remote_sdp = neg->neg_remote_sdp;
1460
1461 /* Increment SDP version */
1462 neg->active_local_sdp->origin.version = ++active_ver;
1463 }
1464 }
1465
1466 /* State is DONE regardless */
1467 neg->state = PJMEDIA_SDP_NEG_STATE_DONE;
1468
1469 /* Save state */
1470 neg->answer_was_remote = neg->has_remote_answer;
1471
1472 /* Clear temporary SDP */
1473 neg->neg_local_sdp = neg->neg_remote_sdp = NULL;
1474 neg->has_remote_answer = PJ_FALSE;
1475
1476 return status;
1477}
1478
1479
1480static pj_status_t custom_fmt_match(pj_pool_t *pool,
1481 const pj_str_t *fmt_name,
1482 pjmedia_sdp_media *offer,
1483 unsigned o_fmt_idx,
1484 pjmedia_sdp_media *answer,
1485 unsigned a_fmt_idx,
1486 unsigned option)
1487{
1488 unsigned i;
1489
1490 for (i = 0; i < fmt_match_cb_cnt; ++i) {
1491 if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0) {
1492 pj_assert(fmt_match_cb[i].cb);
1493 return (*fmt_match_cb[i].cb)(pool, offer, o_fmt_idx,
1494 answer, a_fmt_idx,
1495 option);
1496 }
1497 }
1498
1499 /* Not customized format matching found, should be matched */
1500 return PJ_SUCCESS;
1501}
1502
1503/* Register customized SDP format negotiation callback function. */
1504PJ_DECL(pj_status_t) pjmedia_sdp_neg_register_fmt_match_cb(
1505 const pj_str_t *fmt_name,
1506 pjmedia_sdp_neg_fmt_match_cb cb)
1507{
1508 struct fmt_match_cb_t *f = NULL;
1509 unsigned i;
1510
1511 PJ_ASSERT_RETURN(fmt_name, PJ_EINVAL);
1512
1513 /* Check if the callback for the format name has been registered */
1514 for (i = 0; i < fmt_match_cb_cnt; ++i) {
1515 if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0)
1516 break;
1517 }
1518
1519 /* Unregistration */
1520
1521 if (cb == NULL) {
1522 if (i == fmt_match_cb_cnt)
1523 return PJ_ENOTFOUND;
1524
1525 pj_array_erase(fmt_match_cb, sizeof(fmt_match_cb[0]),
1526 fmt_match_cb_cnt, i);
1527 fmt_match_cb_cnt--;
1528
1529 return PJ_SUCCESS;
1530 }
1531
1532 /* Registration */
1533
1534 if (i < fmt_match_cb_cnt) {
1535 /* The same format name has been registered before */
1536 if (cb != fmt_match_cb[i].cb)
1537 return PJ_EEXISTS;
1538 else
1539 return PJ_SUCCESS;
1540 }
1541
1542 if (fmt_match_cb_cnt >= PJ_ARRAY_SIZE(fmt_match_cb))
1543 return PJ_ETOOMANY;
1544
1545 f = &fmt_match_cb[fmt_match_cb_cnt++];
1546 f->fmt_name = *fmt_name;
1547 f->cb = cb;
1548
1549 return PJ_SUCCESS;
1550}
1551
1552
1553/* Match format in the SDP media offer and answer. */
1554PJ_DEF(pj_status_t) pjmedia_sdp_neg_fmt_match(pj_pool_t *pool,
1555 pjmedia_sdp_media *offer,
1556 unsigned o_fmt_idx,
1557 pjmedia_sdp_media *answer,
1558 unsigned a_fmt_idx,
1559 unsigned option)
1560{
1561 const pjmedia_sdp_attr *attr;
1562 pjmedia_sdp_rtpmap o_rtpmap, a_rtpmap;
1563 unsigned o_pt;
1564 unsigned a_pt;
1565
1566 o_pt = pj_strtoul(&offer->desc.fmt[o_fmt_idx]);
1567 a_pt = pj_strtoul(&answer->desc.fmt[a_fmt_idx]);
1568
1569 if (o_pt < 96 || a_pt < 96) {
1570 if (o_pt == a_pt)
1571 return PJ_SUCCESS;
1572 else
1573 return PJMEDIA_SDP_EFORMATNOTEQUAL;
1574 }
1575
1576 /* Get the format rtpmap from the offer. */
1577 attr = pjmedia_sdp_media_find_attr2(offer, "rtpmap",
1578 &offer->desc.fmt[o_fmt_idx]);
1579 if (!attr) {
1580 pj_assert(!"Bug! Offer haven't been validated");
1581 return PJ_EBUG;
1582 }
1583 pjmedia_sdp_attr_get_rtpmap(attr, &o_rtpmap);
1584
1585 /* Get the format rtpmap from the answer. */
1586 attr = pjmedia_sdp_media_find_attr2(answer, "rtpmap",
1587 &answer->desc.fmt[a_fmt_idx]);
1588 if (!attr) {
1589 pj_assert(!"Bug! Answer haven't been validated");
1590 return PJ_EBUG;
1591 }
1592 pjmedia_sdp_attr_get_rtpmap(attr, &a_rtpmap);
1593
1594 if (pj_stricmp(&o_rtpmap.enc_name, &a_rtpmap.enc_name) != 0 ||
1595 o_rtpmap.clock_rate != a_rtpmap.clock_rate)
1596 {
1597 return PJMEDIA_SDP_EFORMATNOTEQUAL;
1598 }
1599
1600 return custom_fmt_match(pool, &o_rtpmap.enc_name,
1601 offer, o_fmt_idx, answer, a_fmt_idx, option);
1602}
1603