1 From 0ed41eb5fd0e4192e1b7dc374f819d17aef3e805 Mon Sep 17 00:00:00 2001
2 From: George Joseph <gtjoseph@users.noreply.github.com>
3 Date: Tue, 21 Dec 2021 19:32:22 -0700
4 Subject: [PATCH] sip_inv: Additional multipart support (#2919) (#2920)
7 pjsip/include/pjsip-ua/sip_inv.h | 108 ++++++++++-
8 pjsip/src/pjsip-ua/sip_inv.c | 240 ++++++++++++++++++++-----
9 pjsip/src/test/inv_offer_answer_test.c | 103 ++++++++++-
10 3 files changed, 394 insertions(+), 57 deletions(-)
12 --- a/pjsip/include/pjsip-ua/sip_inv.h
13 +++ b/pjsip/include/pjsip-ua/sip_inv.h
14 @@ -451,11 +451,11 @@ struct pjsip_inv_session
18 - * This structure represents SDP information in a pjsip_rx_data. Application
19 - * retrieve this information by calling #pjsip_rdata_get_sdp_info(). This
20 + * This structure represents SDP information in a pjsip_(rx|tx)_data. Application
21 + * retrieve this information by calling #pjsip_get_sdp_info(). This
22 * mechanism supports multipart message body.
24 -typedef struct pjsip_rdata_sdp_info
25 +typedef struct pjsip_sdp_info
28 * Pointer and length of the text body in the incoming message. If
29 @@ -475,7 +475,15 @@ typedef struct pjsip_rdata_sdp_info
31 pjmedia_sdp_session *sdp;
33 -} pjsip_rdata_sdp_info;
37 + * For backwards compatibility and completeness,
38 + * pjsip_rdata_sdp_info and pjsip_tdata_sdp_info
39 + * are typedef'd to pjsip_sdp_info.
41 +typedef pjsip_sdp_info pjsip_rdata_sdp_info;
42 +typedef pjsip_sdp_info pjsip_tdata_sdp_info;
46 @@ -1046,6 +1054,44 @@ PJ_DECL(pj_status_t) pjsip_create_sdp_bo
47 pjsip_msg_body **p_body);
50 + * This is a utility function to create a multipart body with the
51 + * SIP body as the first part.
53 + * @param pool Pool to allocate memory.
54 + * @param sdp SDP session to be put in the SIP message body.
55 + * @param p_body Pointer to receive SIP message body containing
58 + * @return PJ_SUCCESS on success.
60 +PJ_DECL(pj_status_t) pjsip_create_multipart_sdp_body( pj_pool_t *pool,
61 + pjmedia_sdp_session *sdp,
62 + pjsip_msg_body **p_body);
65 + * Retrieve SDP information from a message body. Application should
66 + * prefer to use this function rather than parsing the SDP manually since
67 + * this function supports multipart message body.
69 + * This function will only parse the SDP once, the first time it is called
70 + * on the same message. Subsequent call on the same message will just pick
71 + * up the already parsed SDP from the message.
73 + * @param pool Pool to allocate memory.
74 + * @param body The message body.
75 + * @param msg_media_type From the rdata or tdata Content-Type header, if available.
76 + * If NULL, the content_type from the body will be used.
77 + * @param search_media_type The media type to search for.
78 + * If NULL, "application/sdp" will be used.
80 + * @return The SDP info.
82 +PJ_DECL(pjsip_sdp_info*) pjsip_get_sdp_info(pj_pool_t *pool,
83 + pjsip_msg_body *body,
84 + pjsip_media_type *msg_media_type,
85 + const pjsip_media_type *search_media_type);
88 * Retrieve SDP information from an incoming message. Application should
89 * prefer to use this function rather than parsing the SDP manually since
90 * this function supports multipart message body.
91 @@ -1061,6 +1107,60 @@ PJ_DECL(pj_status_t) pjsip_create_sdp_bo
92 PJ_DECL(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata);
96 + * Retrieve SDP information from an incoming message. Application should
97 + * prefer to use this function rather than parsing the SDP manually since
98 + * this function supports multipart message body.
100 + * This function will only parse the SDP once, the first time it is called
101 + * on the same message. Subsequent call on the same message will just pick
102 + * up the already parsed SDP from the message.
104 + * @param rdata The incoming message.
105 + * @param search_media_type The SDP media type to search for.
106 + * If NULL, "application/sdp" will be used.
108 + * @return The SDP info.
110 +PJ_DECL(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info2(
111 + pjsip_rx_data *rdata,
112 + const pjsip_media_type *search_media_type);
115 + * Retrieve SDP information from an outgoing message. Application should
116 + * prefer to use this function rather than parsing the SDP manually since
117 + * this function supports multipart message body.
119 + * This function will only parse the SDP once, the first time it is called
120 + * on the same message. Subsequent call on the same message will just pick
121 + * up the already parsed SDP from the message.
123 + * @param tdata The outgoing message.
125 + * @return The SDP info.
127 +PJ_DECL(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info(pjsip_tx_data *tdata);
130 + * Retrieve SDP information from an outgoing message. Application should
131 + * prefer to use this function rather than parsing the SDP manually since
132 + * this function supports multipart message body.
134 + * This function will only parse the SDP once, the first time it is called
135 + * on the same message. Subsequent call on the same message will just pick
136 + * up the already parsed SDP from the message.
138 + * @param tdata The outgoing message.
139 + * @param search_media_type The SDP media type to search for.
140 + * If NULL, "application/sdp" will be used.
142 + * @return The SDP info.
144 +PJ_DECL(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info2(
145 + pjsip_tx_data *tdata,
146 + const pjsip_media_type *search_media_type);
152 --- a/pjsip/src/pjsip-ua/sip_inv.c
153 +++ b/pjsip/src/pjsip-ua/sip_inv.c
154 @@ -118,6 +118,8 @@ static pj_status_t handle_timer_response
155 static pj_bool_t inv_check_secure_dlg(pjsip_inv_session *inv,
158 +static int print_sdp(pjsip_msg_body *body, char *buf, pj_size_t len);
160 static void (*inv_state_handler[])( pjsip_inv_session *inv, pjsip_event *e) =
163 @@ -956,66 +958,170 @@ PJ_DEF(pj_status_t) pjsip_inv_create_uac
167 -PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata)
169 - pjsip_rdata_sdp_info *sdp_info;
170 - pjsip_msg_body *body = rdata->msg_info.msg->body;
171 - pjsip_ctype_hdr *ctype_hdr = rdata->msg_info.ctype;
172 - pjsip_media_type app_sdp;
173 +PJ_DEF(pjsip_sdp_info*) pjsip_get_sdp_info(pj_pool_t *pool,
174 + pjsip_msg_body *body,
175 + pjsip_media_type *msg_media_type,
176 + const pjsip_media_type *search_media_type)
178 + pjsip_sdp_info *sdp_info;
179 + pjsip_media_type search_type;
180 + pjsip_media_type multipart_mixed;
181 + pjsip_media_type multipart_alternative;
182 + pjsip_media_type *msg_type;
183 + pj_status_t status;
185 - sdp_info = (pjsip_rdata_sdp_info*)
186 - rdata->endpt_info.mod_data[mod_inv.mod.id];
189 + sdp_info = PJ_POOL_ZALLOC_T(pool,
192 - sdp_info = PJ_POOL_ZALLOC_T(rdata->tp_info.pool,
193 - pjsip_rdata_sdp_info);
194 PJ_ASSERT_RETURN(mod_inv.mod.id >= 0, sdp_info);
195 - rdata->endpt_info.mod_data[mod_inv.mod.id] = sdp_info;
197 - pjsip_media_type_init2(&app_sdp, "application", "sdp");
202 - if (body && ctype_hdr &&
203 - pj_stricmp(&ctype_hdr->media.type, &app_sdp.type)==0 &&
204 - pj_stricmp(&ctype_hdr->media.subtype, &app_sdp.subtype)==0)
205 + if (msg_media_type) {
206 + msg_type = msg_media_type;
208 + if (body->content_type.type.slen == 0) {
211 + msg_type = &body->content_type;
214 + if (!search_media_type) {
215 + pjsip_media_type_init2(&search_type, "application", "sdp");
217 + pj_memcpy(&search_type, search_media_type, sizeof(search_type));
220 + pjsip_media_type_init2(&multipart_mixed, "multipart", "mixed");
221 + pjsip_media_type_init2(&multipart_alternative, "multipart", "alternative");
223 + if (pjsip_media_type_cmp(msg_type, &search_type, PJ_FALSE) == 0)
225 - sdp_info->body.ptr = (char*)body->data;
226 - sdp_info->body.slen = body->len;
227 - } else if (body && ctype_hdr &&
228 - pj_stricmp2(&ctype_hdr->media.type, "multipart")==0 &&
229 - (pj_stricmp2(&ctype_hdr->media.subtype, "mixed")==0 ||
230 - pj_stricmp2(&ctype_hdr->media.subtype, "alternative")==0))
232 + * If the print_body function is print_sdp, we know that
233 + * body->data is a pjmedia_sdp_session object and came from
234 + * a tx_data. If not, it's the text representation of the
235 + * sdp from an rx_data.
237 + if (body->print_body == print_sdp) {
238 + sdp_info->sdp = body->data;
240 + sdp_info->body.ptr = (char*)body->data;
241 + sdp_info->body.slen = body->len;
243 + } else if (pjsip_media_type_cmp(&multipart_mixed, msg_type, PJ_FALSE) == 0 ||
244 + pjsip_media_type_cmp(&multipart_alternative, msg_type, PJ_FALSE) == 0)
246 - pjsip_multipart_part *part;
247 + pjsip_multipart_part *part;
248 + part = pjsip_multipart_find_part(body, &search_type, NULL);
250 + if (part->body->print_body == print_sdp) {
251 + sdp_info->sdp = part->body->data;
253 + sdp_info->body.ptr = (char*)part->body->data;
254 + sdp_info->body.slen = part->body->len;
259 - part = pjsip_multipart_find_part(body, &app_sdp, NULL);
261 - sdp_info->body.ptr = (char*)part->body->data;
262 - sdp_info->body.slen = part->body->len;
265 + * If the body was already a pjmedia_sdp_session, we can just
266 + * return it. If not and there wasn't a text representation
267 + * of the sdp either, we can also just return.
269 + if (sdp_info->sdp || !sdp_info->body.ptr) {
273 - if (sdp_info->body.ptr) {
274 - pj_status_t status;
275 - status = pjmedia_sdp_parse(rdata->tp_info.pool,
276 - sdp_info->body.ptr,
277 - sdp_info->body.slen,
279 - if (status == PJ_SUCCESS)
280 - status = pjmedia_sdp_validate2(sdp_info->sdp, PJ_FALSE);
282 + * If the body was the text representation of teh SDP, we need
283 + * to parse it to create a pjmedia_sdp_session object.
285 + status = pjmedia_sdp_parse(pool,
286 + sdp_info->body.ptr,
287 + sdp_info->body.slen,
289 + if (status == PJ_SUCCESS)
290 + status = pjmedia_sdp_validate2(sdp_info->sdp, PJ_FALSE);
292 - if (status != PJ_SUCCESS) {
293 - sdp_info->sdp = NULL;
294 - PJ_PERROR(1,(THIS_FILE, status,
295 - "Error parsing/validating SDP body"));
297 + if (status != PJ_SUCCESS) {
298 + sdp_info->sdp = NULL;
299 + PJ_PERROR(1, (THIS_FILE, status,
300 + "Error parsing/validating SDP body"));
303 + sdp_info->sdp_err = status;
308 +PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info2(
309 + pjsip_rx_data *rdata,
310 + const pjsip_media_type *search_media_type)
312 + pjsip_media_type *msg_media_type = NULL;
313 + pjsip_rdata_sdp_info *sdp_info;
315 - sdp_info->sdp_err = status;
316 + if (rdata->endpt_info.mod_data[mod_inv.mod.id]) {
317 + return (pjsip_rdata_sdp_info *)rdata->endpt_info.mod_data[mod_inv.mod.id];
321 + * rdata should have a Content-Type header at this point but we'll
324 + if (rdata->msg_info.ctype) {
325 + msg_media_type = &rdata->msg_info.ctype->media;
327 + sdp_info = pjsip_get_sdp_info(rdata->tp_info.pool,
328 + rdata->msg_info.msg->body,
330 + search_media_type);
331 + rdata->endpt_info.mod_data[mod_inv.mod.id] = sdp_info;
336 +PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata)
338 + return pjsip_rdata_get_sdp_info2(rdata, NULL);
341 +PJ_DEF(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info2(
342 + pjsip_tx_data *tdata,
343 + const pjsip_media_type *search_media_type)
345 + pjsip_ctype_hdr *ctype_hdr = NULL;
346 + pjsip_media_type *msg_media_type = NULL;
347 + pjsip_tdata_sdp_info *sdp_info;
349 + if (tdata->mod_data[mod_inv.mod.id]) {
350 + return (pjsip_tdata_sdp_info *)tdata->mod_data[mod_inv.mod.id];
353 + * tdata won't usually have a Content-Type header at this point
354 + * but we'll check just the same,
356 + ctype_hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTENT_TYPE, NULL);
358 + msg_media_type = &ctype_hdr->media;
361 + sdp_info = pjsip_get_sdp_info(tdata->pool,
364 + search_media_type);
365 + tdata->mod_data[mod_inv.mod.id] = sdp_info;
370 +PJ_DEF(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info(pjsip_tx_data *tdata)
372 + return pjsip_tdata_get_sdp_info2(tdata, NULL);
376 * Verify incoming INVITE request.
377 @@ -1740,13 +1846,55 @@ PJ_DEF(pj_status_t) pjsip_create_sdp_bod
381 +static pjsip_multipart_part* create_sdp_part(pj_pool_t *pool, pjmedia_sdp_session *sdp)
383 + pjsip_multipart_part *sdp_part;
384 + pjsip_media_type media_type;
386 + pjsip_media_type_init2(&media_type, "application", "sdp");
388 + sdp_part = pjsip_multipart_create_part(pool);
389 + PJ_ASSERT_RETURN(sdp_part != NULL, NULL);
391 + sdp_part->body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
392 + PJ_ASSERT_RETURN(sdp_part->body != NULL, NULL);
394 + pjsip_media_type_cp(pool, &sdp_part->body->content_type, &media_type);
396 + sdp_part->body->data = sdp;
397 + sdp_part->body->clone_data = clone_sdp;
398 + sdp_part->body->print_body = print_sdp;
403 +PJ_DEF(pj_status_t) pjsip_create_multipart_sdp_body(pj_pool_t *pool,
404 + pjmedia_sdp_session *sdp,
405 + pjsip_msg_body **p_body)
407 + pjsip_media_type media_type;
408 + pjsip_msg_body *multipart;
409 + pjsip_multipart_part *sdp_part;
411 + pjsip_media_type_init2(&media_type, "multipart", "mixed");
412 + multipart = pjsip_multipart_create(pool, &media_type, NULL);
413 + PJ_ASSERT_RETURN(multipart != NULL, PJ_ENOMEM);
415 + sdp_part = create_sdp_part(pool, sdp);
416 + PJ_ASSERT_RETURN(sdp_part != NULL, PJ_ENOMEM);
417 + pjsip_multipart_add_part(pool, multipart, sdp_part);
418 + *p_body = multipart;
423 static pjsip_msg_body *create_sdp_body(pj_pool_t *pool,
424 const pjmedia_sdp_session *c_sdp)
426 pjsip_msg_body *body;
429 - status = pjsip_create_sdp_body(pool,
430 + status = pjsip_create_sdp_body(pool,
431 pjmedia_sdp_session_clone(pool, c_sdp),
434 @@ -2069,6 +2217,7 @@ static pj_status_t inv_check_sdp_in_inco
438 + pjsip_sdp_info *tdata_sdp_info;
439 const pjmedia_sdp_session *reoffer_sdp = NULL;
441 PJ_LOG(4,(inv->obj_name, "Received %s response "
442 @@ -2077,14 +2226,15 @@ static pj_status_t inv_check_sdp_in_inco
443 (st_code/10==18? "early" : "final" )));
445 /* Retrieve original SDP offer from INVITE request */
446 - reoffer_sdp = (const pjmedia_sdp_session*)
447 - tsx->last_tx->msg->body->data;
448 + tdata_sdp_info = pjsip_tdata_get_sdp_info(tsx->last_tx);
449 + reoffer_sdp = tdata_sdp_info->sdp;
451 /* Feed the original offer to negotiator */
452 status = pjmedia_sdp_neg_modify_local_offer2(inv->pool_prov,
457 if (status != PJ_SUCCESS) {
458 PJ_LOG(1,(inv->obj_name, "Error updating local offer for "
459 "forked 2xx/18x response (err=%d)", status));
460 --- a/pjsip/src/test/inv_offer_answer_test.c
461 +++ b/pjsip/src/test/inv_offer_answer_test.c
462 @@ -137,6 +137,7 @@ typedef struct inv_test_param_t
463 pj_bool_t need_established;
466 + pj_bool_t multipart_body;
469 typedef struct inv_test_t
470 @@ -257,6 +258,17 @@ static void on_media_update(pjsip_inv_se
474 + /* Special handling for standard offer/answer */
475 + if (inv_test.param.count == 1 &&
476 + inv_test.param.oa[0] == OFFERER_UAC &&
477 + inv_test.param.need_established)
479 + jobs[job_cnt].type = ESTABLISH_CALL;
480 + jobs[job_cnt].who = PJSIP_ROLE_UAS;
482 + TRACE_((THIS_FILE, " C+++"));
485 pj_assert(job_cnt <= PJ_ARRAY_SIZE(jobs));
488 @@ -333,6 +345,15 @@ static pj_bool_t on_rx_request(pjsip_rx_
490 pj_assert(status == PJ_SUCCESS);
492 + /* Use multipart body, if configured */
493 + if (sdp && inv_test.param.multipart_body) {
494 + status = pjsip_create_multipart_sdp_body(
496 + pjmedia_sdp_session_clone(tdata->pool, sdp),
497 + &tdata->msg->body);
499 + pj_assert(status == PJ_SUCCESS);
501 status = pjsip_inv_send_msg(inv_test.uas, tdata);
502 pj_assert(status == PJ_SUCCESS);
504 @@ -426,6 +447,7 @@ static int perform_test(inv_test_param_t
507 status = pjsip_inv_create_uac(dlg, sdp, inv_test.param.inv_option, &inv_test.uac);
508 + //inv_test.uac->create_multipart = param->multipart_body;
509 PJ_ASSERT_RETURN(status==PJ_SUCCESS, -20);
511 TRACE_((THIS_FILE, " Sending INVITE %s offer", (sdp ? "with" : "without")));
512 @@ -436,8 +458,17 @@ static int perform_test(inv_test_param_t
513 status = pjsip_inv_invite(inv_test.uac, &tdata);
514 PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30);
516 + /* Use multipart body, if configured */
517 + if (sdp && param->multipart_body) {
518 + status = pjsip_create_multipart_sdp_body(
520 + pjmedia_sdp_session_clone(tdata->pool, sdp),
521 + &tdata->msg->body);
523 + PJ_ASSERT_RETURN(status==PJ_SUCCESS, -40);
525 status = pjsip_inv_send_msg(inv_test.uac, tdata);
526 - PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30);
527 + PJ_ASSERT_RETURN(status==PJ_SUCCESS, -50);
530 * Wait until test completes
531 @@ -525,13 +556,14 @@ static inv_test_param_t test_params[] =
532 200/INVITE (answer) <--
538 "Standard INVITE with offer",
548 @@ -539,7 +571,25 @@ static inv_test_param_t test_params[] =
549 PJSIP_INV_REQUIRE_100REL,
557 + "Standard INVITE with offer, with Multipart",
566 + "Standard INVITE with offer, with 100rel, with Multipart",
567 + PJSIP_INV_REQUIRE_100REL,
575 @@ -555,7 +605,8 @@ static inv_test_param_t test_params[] =
585 @@ -563,7 +614,25 @@ static inv_test_param_t test_params[] =
586 PJSIP_INV_REQUIRE_100REL,
594 + "INVITE with no offer, with Multipart",
603 + "INVITE with no offer, with 100rel, with Multipart",
604 + PJSIP_INV_REQUIRE_100REL,
612 @@ -584,14 +653,24 @@ static inv_test_param_t test_params[] =
616 - { OFFERER_UAC, OFFERER_UAC }
617 + { OFFERER_UAC, OFFERER_UAC },
621 + "INVITE and UPDATE by UAC, with Multipart",
625 + { OFFERER_UAC, OFFERER_UAC },
629 "INVITE and UPDATE by UAC, with 100rel",
630 PJSIP_INV_REQUIRE_100REL,
633 - { OFFERER_UAC, OFFERER_UAC }
634 + { OFFERER_UAC, OFFERER_UAC },
639 @@ -617,6 +696,14 @@ static inv_test_param_t test_params[] =
641 { OFFERER_UAC, OFFERER_UAS, OFFERER_UAC, OFFERER_UAS }
644 + "INVITE and many UPDATE by UAC and UAS, with Multipart",
648 + { OFFERER_UAC, OFFERER_UAS, OFFERER_UAC, OFFERER_UAS },