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