From 0ed41eb5fd0e4192e1b7dc374f819d17aef3e805 Mon Sep 17 00:00:00 2001 From: George Joseph Date: Tue, 21 Dec 2021 19:32:22 -0700 Subject: [PATCH] sip_inv: Additional multipart support (#2919) (#2920) --- pjsip/include/pjsip-ua/sip_inv.h | 108 ++++++++++- pjsip/src/pjsip-ua/sip_inv.c | 240 ++++++++++++++++++++----- pjsip/src/test/inv_offer_answer_test.c | 103 ++++++++++- 3 files changed, 394 insertions(+), 57 deletions(-) diff --git a/pjsip/include/pjsip-ua/sip_inv.h b/pjsip/include/pjsip-ua/sip_inv.h index 14f2d23fa..c33551786 100644 --- a/pjsip/include/pjsip-ua/sip_inv.h +++ b/pjsip/include/pjsip-ua/sip_inv.h @@ -451,11 +451,11 @@ struct pjsip_inv_session /** - * This structure represents SDP information in a pjsip_rx_data. Application - * retrieve this information by calling #pjsip_rdata_get_sdp_info(). This + * This structure represents SDP information in a pjsip_(rx|tx)_data. Application + * retrieve this information by calling #pjsip_get_sdp_info(). This * mechanism supports multipart message body. */ -typedef struct pjsip_rdata_sdp_info +typedef struct pjsip_sdp_info { /** * Pointer and length of the text body in the incoming message. If @@ -475,7 +475,15 @@ typedef struct pjsip_rdata_sdp_info */ pjmedia_sdp_session *sdp; -} pjsip_rdata_sdp_info; +} pjsip_sdp_info; + +/** + * For backwards compatibility and completeness, + * pjsip_rdata_sdp_info and pjsip_tdata_sdp_info + * are typedef'd to pjsip_sdp_info. + */ +typedef pjsip_sdp_info pjsip_rdata_sdp_info; +typedef pjsip_sdp_info pjsip_tdata_sdp_info; /** @@ -1045,6 +1053,44 @@ PJ_DECL(pj_status_t) pjsip_create_sdp_body(pj_pool_t *pool, pjmedia_sdp_session *sdp, pjsip_msg_body **p_body); +/** + * This is a utility function to create a multipart body with the + * SIP body as the first part. + * + * @param pool Pool to allocate memory. + * @param sdp SDP session to be put in the SIP message body. + * @param p_body Pointer to receive SIP message body containing + * the SDP session. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_create_multipart_sdp_body( pj_pool_t *pool, + pjmedia_sdp_session *sdp, + pjsip_msg_body **p_body); + +/** + * Retrieve SDP information from a message body. Application should + * prefer to use this function rather than parsing the SDP manually since + * this function supports multipart message body. + * + * This function will only parse the SDP once, the first time it is called + * on the same message. Subsequent call on the same message will just pick + * up the already parsed SDP from the message. + * + * @param pool Pool to allocate memory. + * @param body The message body. + * @param msg_media_type From the rdata or tdata Content-Type header, if available. + * If NULL, the content_type from the body will be used. + * @param search_media_type The media type to search for. + * If NULL, "application/sdp" will be used. + * + * @return The SDP info. + */ +PJ_DECL(pjsip_sdp_info*) pjsip_get_sdp_info(pj_pool_t *pool, + pjsip_msg_body *body, + pjsip_media_type *msg_media_type, + const pjsip_media_type *search_media_type); + /** * Retrieve SDP information from an incoming message. Application should * prefer to use this function rather than parsing the SDP manually since @@ -1061,6 +1107,60 @@ PJ_DECL(pj_status_t) pjsip_create_sdp_body(pj_pool_t *pool, PJ_DECL(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata); +/** + * Retrieve SDP information from an incoming message. Application should + * prefer to use this function rather than parsing the SDP manually since + * this function supports multipart message body. + * + * This function will only parse the SDP once, the first time it is called + * on the same message. Subsequent call on the same message will just pick + * up the already parsed SDP from the message. + * + * @param rdata The incoming message. + * @param search_media_type The SDP media type to search for. + * If NULL, "application/sdp" will be used. + * + * @return The SDP info. + */ +PJ_DECL(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info2( + pjsip_rx_data *rdata, + const pjsip_media_type *search_media_type); + +/** + * Retrieve SDP information from an outgoing message. Application should + * prefer to use this function rather than parsing the SDP manually since + * this function supports multipart message body. + * + * This function will only parse the SDP once, the first time it is called + * on the same message. Subsequent call on the same message will just pick + * up the already parsed SDP from the message. + * + * @param tdata The outgoing message. + * + * @return The SDP info. + */ +PJ_DECL(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info(pjsip_tx_data *tdata); + +/** + * Retrieve SDP information from an outgoing message. Application should + * prefer to use this function rather than parsing the SDP manually since + * this function supports multipart message body. + * + * This function will only parse the SDP once, the first time it is called + * on the same message. Subsequent call on the same message will just pick + * up the already parsed SDP from the message. + * + * @param tdata The outgoing message. + * @param search_media_type The SDP media type to search for. + * If NULL, "application/sdp" will be used. + * + * @return The SDP info. + */ +PJ_DECL(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info2( + pjsip_tx_data *tdata, + const pjsip_media_type *search_media_type); + + PJ_END_DECL /** diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c index ca225015b..b68ae0f16 100644 --- a/pjsip/src/pjsip-ua/sip_inv.c +++ b/pjsip/src/pjsip-ua/sip_inv.c @@ -118,6 +118,8 @@ static pj_status_t handle_timer_response(pjsip_inv_session *inv, static pj_bool_t inv_check_secure_dlg(pjsip_inv_session *inv, pjsip_event *e); +static int print_sdp(pjsip_msg_body *body, char *buf, pj_size_t len); + static void (*inv_state_handler[])( pjsip_inv_session *inv, pjsip_event *e) = { &inv_on_state_null, @@ -946,66 +948,170 @@ PJ_DEF(pj_status_t) pjsip_inv_create_uac( pjsip_dialog *dlg, return PJ_SUCCESS; } -PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata) +PJ_DEF(pjsip_sdp_info*) pjsip_get_sdp_info(pj_pool_t *pool, + pjsip_msg_body *body, + pjsip_media_type *msg_media_type, + const pjsip_media_type *search_media_type) { - pjsip_rdata_sdp_info *sdp_info; - pjsip_msg_body *body = rdata->msg_info.msg->body; - pjsip_ctype_hdr *ctype_hdr = rdata->msg_info.ctype; - pjsip_media_type app_sdp; + pjsip_sdp_info *sdp_info; + pjsip_media_type search_type; + pjsip_media_type multipart_mixed; + pjsip_media_type multipart_alternative; + pjsip_media_type *msg_type; + pj_status_t status; - sdp_info = (pjsip_rdata_sdp_info*) - rdata->endpt_info.mod_data[mod_inv.mod.id]; - if (sdp_info) - return sdp_info; + sdp_info = PJ_POOL_ZALLOC_T(pool, + pjsip_sdp_info); - sdp_info = PJ_POOL_ZALLOC_T(rdata->tp_info.pool, - pjsip_rdata_sdp_info); PJ_ASSERT_RETURN(mod_inv.mod.id >= 0, sdp_info); - rdata->endpt_info.mod_data[mod_inv.mod.id] = sdp_info; - pjsip_media_type_init2(&app_sdp, "application", "sdp"); + if (!body) { + return sdp_info; + } - if (body && ctype_hdr && - pj_stricmp(&ctype_hdr->media.type, &app_sdp.type)==0 && - pj_stricmp(&ctype_hdr->media.subtype, &app_sdp.subtype)==0) + if (msg_media_type) { + msg_type = msg_media_type; + } else { + if (body->content_type.type.slen == 0) { + return sdp_info; + } + msg_type = &body->content_type; + } + + if (!search_media_type) { + pjsip_media_type_init2(&search_type, "application", "sdp"); + } else { + pj_memcpy(&search_type, search_media_type, sizeof(search_type)); + } + + pjsip_media_type_init2(&multipart_mixed, "multipart", "mixed"); + pjsip_media_type_init2(&multipart_alternative, "multipart", "alternative"); + + if (pjsip_media_type_cmp(msg_type, &search_type, PJ_FALSE) == 0) { - sdp_info->body.ptr = (char*)body->data; - sdp_info->body.slen = body->len; - } else if (body && ctype_hdr && - pj_stricmp2(&ctype_hdr->media.type, "multipart")==0 && - (pj_stricmp2(&ctype_hdr->media.subtype, "mixed")==0 || - pj_stricmp2(&ctype_hdr->media.subtype, "alternative")==0)) + /* + * If the print_body function is print_sdp, we know that + * body->data is a pjmedia_sdp_session object and came from + * a tx_data. If not, it's the text representation of the + * sdp from an rx_data. + */ + if (body->print_body == print_sdp) { + sdp_info->sdp = body->data; + } else { + sdp_info->body.ptr = (char*)body->data; + sdp_info->body.slen = body->len; + } + } else if (pjsip_media_type_cmp(&multipart_mixed, msg_type, PJ_FALSE) == 0 || + pjsip_media_type_cmp(&multipart_alternative, msg_type, PJ_FALSE) == 0) { - pjsip_multipart_part *part; + pjsip_multipart_part *part; + part = pjsip_multipart_find_part(body, &search_type, NULL); + if (part) { + if (part->body->print_body == print_sdp) { + sdp_info->sdp = part->body->data; + } else { + sdp_info->body.ptr = (char*)part->body->data; + sdp_info->body.slen = part->body->len; + } + } + } - part = pjsip_multipart_find_part(body, &app_sdp, NULL); - if (part) { - sdp_info->body.ptr = (char*)part->body->data; - sdp_info->body.slen = part->body->len; - } + /* + * If the body was already a pjmedia_sdp_session, we can just + * return it. If not and there wasn't a text representation + * of the sdp either, we can also just return. + */ + if (sdp_info->sdp || !sdp_info->body.ptr) { + return sdp_info; } - if (sdp_info->body.ptr) { - pj_status_t status; - status = pjmedia_sdp_parse(rdata->tp_info.pool, - sdp_info->body.ptr, - sdp_info->body.slen, - &sdp_info->sdp); - if (status == PJ_SUCCESS) - status = pjmedia_sdp_validate2(sdp_info->sdp, PJ_FALSE); + /* + * If the body was the text representation of teh SDP, we need + * to parse it to create a pjmedia_sdp_session object. + */ + status = pjmedia_sdp_parse(pool, + sdp_info->body.ptr, + sdp_info->body.slen, + &sdp_info->sdp); + if (status == PJ_SUCCESS) + status = pjmedia_sdp_validate2(sdp_info->sdp, PJ_FALSE); - if (status != PJ_SUCCESS) { - sdp_info->sdp = NULL; - PJ_PERROR(1,(THIS_FILE, status, - "Error parsing/validating SDP body")); - } + if (status != PJ_SUCCESS) { + sdp_info->sdp = NULL; + PJ_PERROR(1, (THIS_FILE, status, + "Error parsing/validating SDP body")); + } + + sdp_info->sdp_err = status; + + return sdp_info; +} - sdp_info->sdp_err = status; +PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info2( + pjsip_rx_data *rdata, + const pjsip_media_type *search_media_type) +{ + pjsip_media_type *msg_media_type = NULL; + pjsip_rdata_sdp_info *sdp_info; + + if (rdata->endpt_info.mod_data[mod_inv.mod.id]) { + return (pjsip_rdata_sdp_info *)rdata->endpt_info.mod_data[mod_inv.mod.id]; + } + + /* + * rdata should have a Content-Type header at this point but we'll + * make sure. + */ + if (rdata->msg_info.ctype) { + msg_media_type = &rdata->msg_info.ctype->media; + } + sdp_info = pjsip_get_sdp_info(rdata->tp_info.pool, + rdata->msg_info.msg->body, + msg_media_type, + search_media_type); + rdata->endpt_info.mod_data[mod_inv.mod.id] = sdp_info; + + return sdp_info; +} + +PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata) +{ + return pjsip_rdata_get_sdp_info2(rdata, NULL); +} + +PJ_DEF(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info2( + pjsip_tx_data *tdata, + const pjsip_media_type *search_media_type) +{ + pjsip_ctype_hdr *ctype_hdr = NULL; + pjsip_media_type *msg_media_type = NULL; + pjsip_tdata_sdp_info *sdp_info; + + if (tdata->mod_data[mod_inv.mod.id]) { + return (pjsip_tdata_sdp_info *)tdata->mod_data[mod_inv.mod.id]; + } + /* + * tdata won't usually have a Content-Type header at this point + * but we'll check just the same, + */ + ctype_hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTENT_TYPE, NULL); + if (ctype_hdr) { + msg_media_type = &ctype_hdr->media; } + sdp_info = pjsip_get_sdp_info(tdata->pool, + tdata->msg->body, + msg_media_type, + search_media_type); + tdata->mod_data[mod_inv.mod.id] = sdp_info; + return sdp_info; } +PJ_DEF(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info(pjsip_tx_data *tdata) +{ + return pjsip_tdata_get_sdp_info2(tdata, NULL); +} /* * Verify incoming INVITE request. @@ -1730,13 +1836,55 @@ PJ_DEF(pj_status_t) pjsip_create_sdp_body( pj_pool_t *pool, return PJ_SUCCESS; } +static pjsip_multipart_part* create_sdp_part(pj_pool_t *pool, pjmedia_sdp_session *sdp) +{ + pjsip_multipart_part *sdp_part; + pjsip_media_type media_type; + + pjsip_media_type_init2(&media_type, "application", "sdp"); + + sdp_part = pjsip_multipart_create_part(pool); + PJ_ASSERT_RETURN(sdp_part != NULL, NULL); + + sdp_part->body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body); + PJ_ASSERT_RETURN(sdp_part->body != NULL, NULL); + + pjsip_media_type_cp(pool, &sdp_part->body->content_type, &media_type); + + sdp_part->body->data = sdp; + sdp_part->body->clone_data = clone_sdp; + sdp_part->body->print_body = print_sdp; + + return sdp_part; +} + +PJ_DEF(pj_status_t) pjsip_create_multipart_sdp_body(pj_pool_t *pool, + pjmedia_sdp_session *sdp, + pjsip_msg_body **p_body) +{ + pjsip_media_type media_type; + pjsip_msg_body *multipart; + pjsip_multipart_part *sdp_part; + + pjsip_media_type_init2(&media_type, "multipart", "mixed"); + multipart = pjsip_multipart_create(pool, &media_type, NULL); + PJ_ASSERT_RETURN(multipart != NULL, PJ_ENOMEM); + + sdp_part = create_sdp_part(pool, sdp); + PJ_ASSERT_RETURN(sdp_part != NULL, PJ_ENOMEM); + pjsip_multipart_add_part(pool, multipart, sdp_part); + *p_body = multipart; + + return PJ_SUCCESS; +} + static pjsip_msg_body *create_sdp_body(pj_pool_t *pool, const pjmedia_sdp_session *c_sdp) { pjsip_msg_body *body; pj_status_t status; - status = pjsip_create_sdp_body(pool, + status = pjsip_create_sdp_body(pool, pjmedia_sdp_session_clone(pool, c_sdp), &body); @@ -2059,6 +2207,7 @@ static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, ) ) { + pjsip_sdp_info *tdata_sdp_info; const pjmedia_sdp_session *reoffer_sdp = NULL; PJ_LOG(4,(inv->obj_name, "Received %s response " @@ -2067,14 +2216,15 @@ static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, (st_code/10==18? "early" : "final" ))); /* Retrieve original SDP offer from INVITE request */ - reoffer_sdp = (const pjmedia_sdp_session*) - tsx->last_tx->msg->body->data; + tdata_sdp_info = pjsip_tdata_get_sdp_info(tsx->last_tx); + reoffer_sdp = tdata_sdp_info->sdp; /* Feed the original offer to negotiator */ status = pjmedia_sdp_neg_modify_local_offer2(inv->pool_prov, inv->neg, inv->sdp_neg_flags, reoffer_sdp); + if (status != PJ_SUCCESS) { PJ_LOG(1,(inv->obj_name, "Error updating local offer for " "forked 2xx/18x response (err=%d)", status)); diff --git a/pjsip/src/test/inv_offer_answer_test.c b/pjsip/src/test/inv_offer_answer_test.c index ad5fcd409..9cdd2654b 100644 --- a/pjsip/src/test/inv_offer_answer_test.c +++ b/pjsip/src/test/inv_offer_answer_test.c @@ -137,6 +137,7 @@ typedef struct inv_test_param_t pj_bool_t need_established; unsigned count; oa_t oa[4]; + pj_bool_t multipart_body; } inv_test_param_t; typedef struct inv_test_t @@ -257,6 +258,17 @@ static void on_media_update(pjsip_inv_session *inv_ses, } } + /* Special handling for standard offer/answer */ + if (inv_test.param.count == 1 && + inv_test.param.oa[0] == OFFERER_UAC && + inv_test.param.need_established) + { + jobs[job_cnt].type = ESTABLISH_CALL; + jobs[job_cnt].who = PJSIP_ROLE_UAS; + job_cnt++; + TRACE_((THIS_FILE, " C+++")); + } + pj_assert(job_cnt <= PJ_ARRAY_SIZE(jobs)); } } @@ -333,6 +345,15 @@ static pj_bool_t on_rx_request(pjsip_rx_data *rdata) NULL, &tdata); pj_assert(status == PJ_SUCCESS); + /* Use multipart body, if configured */ + if (sdp && inv_test.param.multipart_body) { + status = pjsip_create_multipart_sdp_body( + tdata->pool, + pjmedia_sdp_session_clone(tdata->pool, sdp), + &tdata->msg->body); + } + pj_assert(status == PJ_SUCCESS); + status = pjsip_inv_send_msg(inv_test.uas, tdata); pj_assert(status == PJ_SUCCESS); @@ -426,6 +447,7 @@ static int perform_test(inv_test_param_t *param) sdp = NULL; status = pjsip_inv_create_uac(dlg, sdp, inv_test.param.inv_option, &inv_test.uac); + //inv_test.uac->create_multipart = param->multipart_body; PJ_ASSERT_RETURN(status==PJ_SUCCESS, -20); TRACE_((THIS_FILE, " Sending INVITE %s offer", (sdp ? "with" : "without"))); @@ -436,8 +458,17 @@ static int perform_test(inv_test_param_t *param) status = pjsip_inv_invite(inv_test.uac, &tdata); PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30); + /* Use multipart body, if configured */ + if (sdp && param->multipart_body) { + status = pjsip_create_multipart_sdp_body( + tdata->pool, + pjmedia_sdp_session_clone(tdata->pool, sdp), + &tdata->msg->body); + } + PJ_ASSERT_RETURN(status==PJ_SUCCESS, -40); + status = pjsip_inv_send_msg(inv_test.uac, tdata); - PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, -50); /* * Wait until test completes @@ -525,13 +556,14 @@ static inv_test_param_t test_params[] = 200/INVITE (answer) <-- ACK --> */ -#if 0 +#if 1 { "Standard INVITE with offer", 0, PJ_TRUE, 1, - { OFFERER_UAC } + { OFFERER_UAC }, + PJ_FALSE }, { @@ -539,7 +571,25 @@ static inv_test_param_t test_params[] = PJSIP_INV_REQUIRE_100REL, PJ_TRUE, 1, - { OFFERER_UAC } + { OFFERER_UAC }, + PJ_FALSE + }, + { + "Standard INVITE with offer, with Multipart", + 0, + PJ_TRUE, + 1, + { OFFERER_UAC }, + PJ_TRUE + }, + + { + "Standard INVITE with offer, with 100rel, with Multipart", + PJSIP_INV_REQUIRE_100REL, + PJ_TRUE, + 1, + { OFFERER_UAC }, + PJ_TRUE }, #endif @@ -555,7 +605,8 @@ static inv_test_param_t test_params[] = 0, PJ_TRUE, 1, - { OFFERER_UAS } + { OFFERER_UAS }, + PJ_FALSE }, { @@ -563,7 +614,25 @@ static inv_test_param_t test_params[] = PJSIP_INV_REQUIRE_100REL, PJ_TRUE, 1, - { OFFERER_UAS } + { OFFERER_UAS }, + PJ_FALSE + }, + { + "INVITE with no offer, with Multipart", + 0, + PJ_TRUE, + 1, + { OFFERER_UAS }, + PJ_TRUE + }, + + { + "INVITE with no offer, with 100rel, with Multipart", + PJSIP_INV_REQUIRE_100REL, + PJ_TRUE, + 1, + { OFFERER_UAS }, + PJ_TRUE }, #endif @@ -584,14 +653,24 @@ static inv_test_param_t test_params[] = 0, PJ_TRUE, 2, - { OFFERER_UAC, OFFERER_UAC } + { OFFERER_UAC, OFFERER_UAC }, + PJ_FALSE + }, + { + "INVITE and UPDATE by UAC, with Multipart", + 0, + PJ_TRUE, + 2, + { OFFERER_UAC, OFFERER_UAC }, + PJ_TRUE }, { "INVITE and UPDATE by UAC, with 100rel", PJSIP_INV_REQUIRE_100REL, PJ_TRUE, 2, - { OFFERER_UAC, OFFERER_UAC } + { OFFERER_UAC, OFFERER_UAC }, + PJ_FALSE }, #endif @@ -617,6 +696,14 @@ static inv_test_param_t test_params[] = 4, { OFFERER_UAC, OFFERER_UAS, OFFERER_UAC, OFFERER_UAS } }, + { + "INVITE and many UPDATE by UAC and UAS, with Multipart", + 0, + PJ_TRUE, + 4, + { OFFERER_UAC, OFFERER_UAS, OFFERER_UAC, OFFERER_UAS }, + PJ_TRUE + }, }; -- 2.33.1