blob: 8db8aab9d01ef3f13a36f9d0bced6096afc66bf0 [file] [log] [blame]
b.liue9582032025-04-17 19:18:16 +08001From 0ed41eb5fd0e4192e1b7dc374f819d17aef3e805 Mon Sep 17 00:00:00 2001
2From: George Joseph <gtjoseph@users.noreply.github.com>
3Date: Tue, 21 Dec 2021 19:32:22 -0700
4Subject: [PATCH] sip_inv: Additional multipart support (#2919) (#2920)
5
6---
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(-)
11
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
15
16
17 /**
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.
23 */
24-typedef struct pjsip_rdata_sdp_info
25+typedef struct pjsip_sdp_info
26 {
27 /**
28 * Pointer and length of the text body in the incoming message. If
29@@ -475,7 +475,15 @@ typedef struct pjsip_rdata_sdp_info
30 */
31 pjmedia_sdp_session *sdp;
32
33-} pjsip_rdata_sdp_info;
34+} pjsip_sdp_info;
35+
36+/**
37+ * For backwards compatibility and completeness,
38+ * pjsip_rdata_sdp_info and pjsip_tdata_sdp_info
39+ * are typedef'd to pjsip_sdp_info.
40+ */
41+typedef pjsip_sdp_info pjsip_rdata_sdp_info;
42+typedef pjsip_sdp_info pjsip_tdata_sdp_info;
43
44
45 /**
46@@ -1046,6 +1054,44 @@ PJ_DECL(pj_status_t) pjsip_create_sdp_bo
47 pjsip_msg_body **p_body);
48
49 /**
50+ * This is a utility function to create a multipart body with the
51+ * SIP body as the first part.
52+ *
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
56+ * the SDP session.
57+ *
58+ * @return PJ_SUCCESS on success.
59+ */
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);
63+
64+/**
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.
68+ *
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.
72+ *
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.
79+ *
80+ * @return The SDP info.
81+ */
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);
86+
87+/**
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);
93
94
95+/**
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.
99+ *
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.
103+ *
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.
107+ *
108+ * @return The SDP info.
109+ */
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);
113+
114+/**
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.
118+ *
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.
122+ *
123+ * @param tdata The outgoing message.
124+ *
125+ * @return The SDP info.
126+ */
127+PJ_DECL(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info(pjsip_tx_data *tdata);
128+
129+/**
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.
133+ *
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.
137+ *
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.
141+ *
142+ * @return The SDP info.
143+ */
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);
147+
148+
149 PJ_END_DECL
150
151 /**
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,
156 pjsip_event *e);
157
158+static int print_sdp(pjsip_msg_body *body, char *buf, pj_size_t len);
159+
160 static void (*inv_state_handler[])( pjsip_inv_session *inv, pjsip_event *e) =
161 {
162 &inv_on_state_null,
163@@ -956,66 +958,170 @@ PJ_DEF(pj_status_t) pjsip_inv_create_uac
164 return PJ_SUCCESS;
165 }
166
167-PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata)
168-{
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)
177+{
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;
184
185- sdp_info = (pjsip_rdata_sdp_info*)
186- rdata->endpt_info.mod_data[mod_inv.mod.id];
187- if (sdp_info)
188- return sdp_info;
189+ sdp_info = PJ_POOL_ZALLOC_T(pool,
190+ pjsip_sdp_info);
191
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;
196
197- pjsip_media_type_init2(&app_sdp, "application", "sdp");
198+ if (!body) {
199+ return sdp_info;
200+ }
201
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;
207+ } else {
208+ if (body->content_type.type.slen == 0) {
209+ return sdp_info;
210+ }
211+ msg_type = &body->content_type;
212+ }
213+
214+ if (!search_media_type) {
215+ pjsip_media_type_init2(&search_type, "application", "sdp");
216+ } else {
217+ pj_memcpy(&search_type, search_media_type, sizeof(search_type));
218+ }
219+
220+ pjsip_media_type_init2(&multipart_mixed, "multipart", "mixed");
221+ pjsip_media_type_init2(&multipart_alternative, "multipart", "alternative");
222+
223+ if (pjsip_media_type_cmp(msg_type, &search_type, PJ_FALSE) == 0)
224 {
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))
231+ /*
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.
236+ */
237+ if (body->print_body == print_sdp) {
238+ sdp_info->sdp = body->data;
239+ } else {
240+ sdp_info->body.ptr = (char*)body->data;
241+ sdp_info->body.slen = body->len;
242+ }
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)
245 {
246- pjsip_multipart_part *part;
247+ pjsip_multipart_part *part;
248+ part = pjsip_multipart_find_part(body, &search_type, NULL);
249+ if (part) {
250+ if (part->body->print_body == print_sdp) {
251+ sdp_info->sdp = part->body->data;
252+ } else {
253+ sdp_info->body.ptr = (char*)part->body->data;
254+ sdp_info->body.slen = part->body->len;
255+ }
256+ }
257+ }
258
259- part = pjsip_multipart_find_part(body, &app_sdp, NULL);
260- if (part) {
261- sdp_info->body.ptr = (char*)part->body->data;
262- sdp_info->body.slen = part->body->len;
263- }
264+ /*
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.
268+ */
269+ if (sdp_info->sdp || !sdp_info->body.ptr) {
270+ return sdp_info;
271 }
272
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,
278- &sdp_info->sdp);
279- if (status == PJ_SUCCESS)
280- status = pjmedia_sdp_validate2(sdp_info->sdp, PJ_FALSE);
281+ /*
282+ * If the body was the text representation of teh SDP, we need
283+ * to parse it to create a pjmedia_sdp_session object.
284+ */
285+ status = pjmedia_sdp_parse(pool,
286+ sdp_info->body.ptr,
287+ sdp_info->body.slen,
288+ &sdp_info->sdp);
289+ if (status == PJ_SUCCESS)
290+ status = pjmedia_sdp_validate2(sdp_info->sdp, PJ_FALSE);
291
292- if (status != PJ_SUCCESS) {
293- sdp_info->sdp = NULL;
294- PJ_PERROR(1,(THIS_FILE, status,
295- "Error parsing/validating SDP body"));
296- }
297+ if (status != PJ_SUCCESS) {
298+ sdp_info->sdp = NULL;
299+ PJ_PERROR(1, (THIS_FILE, status,
300+ "Error parsing/validating SDP body"));
301+ }
302+
303+ sdp_info->sdp_err = status;
304+
305+ return sdp_info;
306+}
307+
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)
311+{
312+ pjsip_media_type *msg_media_type = NULL;
313+ pjsip_rdata_sdp_info *sdp_info;
314
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];
318+ }
319+
320+ /*
321+ * rdata should have a Content-Type header at this point but we'll
322+ * make sure.
323+ */
324+ if (rdata->msg_info.ctype) {
325+ msg_media_type = &rdata->msg_info.ctype->media;
326 }
327+ sdp_info = pjsip_get_sdp_info(rdata->tp_info.pool,
328+ rdata->msg_info.msg->body,
329+ msg_media_type,
330+ search_media_type);
331+ rdata->endpt_info.mod_data[mod_inv.mod.id] = sdp_info;
332
333 return sdp_info;
334 }
335
336+PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata)
337+{
338+ return pjsip_rdata_get_sdp_info2(rdata, NULL);
339+}
340+
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)
344+{
345+ pjsip_ctype_hdr *ctype_hdr = NULL;
346+ pjsip_media_type *msg_media_type = NULL;
347+ pjsip_tdata_sdp_info *sdp_info;
348+
349+ if (tdata->mod_data[mod_inv.mod.id]) {
350+ return (pjsip_tdata_sdp_info *)tdata->mod_data[mod_inv.mod.id];
351+ }
352+ /*
353+ * tdata won't usually have a Content-Type header at this point
354+ * but we'll check just the same,
355+ */
356+ ctype_hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTENT_TYPE, NULL);
357+ if (ctype_hdr) {
358+ msg_media_type = &ctype_hdr->media;
359+ }
360+
361+ sdp_info = pjsip_get_sdp_info(tdata->pool,
362+ tdata->msg->body,
363+ msg_media_type,
364+ search_media_type);
365+ tdata->mod_data[mod_inv.mod.id] = sdp_info;
366+
367+ return sdp_info;
368+}
369+
370+PJ_DEF(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info(pjsip_tx_data *tdata)
371+{
372+ return pjsip_tdata_get_sdp_info2(tdata, NULL);
373+}
374
375 /*
376 * Verify incoming INVITE request.
377@@ -1740,13 +1846,55 @@ PJ_DEF(pj_status_t) pjsip_create_sdp_bod
378 return PJ_SUCCESS;
379 }
380
381+static pjsip_multipart_part* create_sdp_part(pj_pool_t *pool, pjmedia_sdp_session *sdp)
382+{
383+ pjsip_multipart_part *sdp_part;
384+ pjsip_media_type media_type;
385+
386+ pjsip_media_type_init2(&media_type, "application", "sdp");
387+
388+ sdp_part = pjsip_multipart_create_part(pool);
389+ PJ_ASSERT_RETURN(sdp_part != NULL, NULL);
390+
391+ sdp_part->body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
392+ PJ_ASSERT_RETURN(sdp_part->body != NULL, NULL);
393+
394+ pjsip_media_type_cp(pool, &sdp_part->body->content_type, &media_type);
395+
396+ sdp_part->body->data = sdp;
397+ sdp_part->body->clone_data = clone_sdp;
398+ sdp_part->body->print_body = print_sdp;
399+
400+ return sdp_part;
401+}
402+
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)
406+{
407+ pjsip_media_type media_type;
408+ pjsip_msg_body *multipart;
409+ pjsip_multipart_part *sdp_part;
410+
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);
414+
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;
419+
420+ return PJ_SUCCESS;
421+}
422+
423 static pjsip_msg_body *create_sdp_body(pj_pool_t *pool,
424 const pjmedia_sdp_session *c_sdp)
425 {
426 pjsip_msg_body *body;
427 pj_status_t status;
428
429- status = pjsip_create_sdp_body(pool,
430+ status = pjsip_create_sdp_body(pool,
431 pjmedia_sdp_session_clone(pool, c_sdp),
432 &body);
433
434@@ -2069,6 +2217,7 @@ static pj_status_t inv_check_sdp_in_inco
435 )
436 )
437 {
438+ pjsip_sdp_info *tdata_sdp_info;
439 const pjmedia_sdp_session *reoffer_sdp = NULL;
440
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" )));
444
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;
450
451 /* Feed the original offer to negotiator */
452 status = pjmedia_sdp_neg_modify_local_offer2(inv->pool_prov,
453 inv->neg,
454 inv->sdp_neg_flags,
455 reoffer_sdp);
456+
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;
464 unsigned count;
465 oa_t oa[4];
466+ pj_bool_t multipart_body;
467 } inv_test_param_t;
468
469 typedef struct inv_test_t
470@@ -257,6 +258,17 @@ static void on_media_update(pjsip_inv_se
471 }
472 }
473
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)
478+ {
479+ jobs[job_cnt].type = ESTABLISH_CALL;
480+ jobs[job_cnt].who = PJSIP_ROLE_UAS;
481+ job_cnt++;
482+ TRACE_((THIS_FILE, " C+++"));
483+ }
484+
485 pj_assert(job_cnt <= PJ_ARRAY_SIZE(jobs));
486 }
487 }
488@@ -333,6 +345,15 @@ static pj_bool_t on_rx_request(pjsip_rx_
489 NULL, &tdata);
490 pj_assert(status == PJ_SUCCESS);
491
492+ /* Use multipart body, if configured */
493+ if (sdp && inv_test.param.multipart_body) {
494+ status = pjsip_create_multipart_sdp_body(
495+ tdata->pool,
496+ pjmedia_sdp_session_clone(tdata->pool, sdp),
497+ &tdata->msg->body);
498+ }
499+ pj_assert(status == PJ_SUCCESS);
500+
501 status = pjsip_inv_send_msg(inv_test.uas, tdata);
502 pj_assert(status == PJ_SUCCESS);
503
504@@ -426,6 +447,7 @@ static int perform_test(inv_test_param_t
505 sdp = NULL;
506
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);
510
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);
515
516+ /* Use multipart body, if configured */
517+ if (sdp && param->multipart_body) {
518+ status = pjsip_create_multipart_sdp_body(
519+ tdata->pool,
520+ pjmedia_sdp_session_clone(tdata->pool, sdp),
521+ &tdata->msg->body);
522+ }
523+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, -40);
524+
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);
528
529 /*
530 * Wait until test completes
531@@ -525,13 +556,14 @@ static inv_test_param_t test_params[] =
532 200/INVITE (answer) <--
533 ACK -->
534 */
535-#if 0
536+#if 1
537 {
538 "Standard INVITE with offer",
539 0,
540 PJ_TRUE,
541 1,
542- { OFFERER_UAC }
543+ { OFFERER_UAC },
544+ PJ_FALSE
545 },
546
547 {
548@@ -539,7 +571,25 @@ static inv_test_param_t test_params[] =
549 PJSIP_INV_REQUIRE_100REL,
550 PJ_TRUE,
551 1,
552- { OFFERER_UAC }
553+ { OFFERER_UAC },
554+ PJ_FALSE
555+ },
556+ {
557+ "Standard INVITE with offer, with Multipart",
558+ 0,
559+ PJ_TRUE,
560+ 1,
561+ { OFFERER_UAC },
562+ PJ_TRUE
563+ },
564+
565+ {
566+ "Standard INVITE with offer, with 100rel, with Multipart",
567+ PJSIP_INV_REQUIRE_100REL,
568+ PJ_TRUE,
569+ 1,
570+ { OFFERER_UAC },
571+ PJ_TRUE
572 },
573 #endif
574
575@@ -555,7 +605,8 @@ static inv_test_param_t test_params[] =
576 0,
577 PJ_TRUE,
578 1,
579- { OFFERER_UAS }
580+ { OFFERER_UAS },
581+ PJ_FALSE
582 },
583
584 {
585@@ -563,7 +614,25 @@ static inv_test_param_t test_params[] =
586 PJSIP_INV_REQUIRE_100REL,
587 PJ_TRUE,
588 1,
589- { OFFERER_UAS }
590+ { OFFERER_UAS },
591+ PJ_FALSE
592+ },
593+ {
594+ "INVITE with no offer, with Multipart",
595+ 0,
596+ PJ_TRUE,
597+ 1,
598+ { OFFERER_UAS },
599+ PJ_TRUE
600+ },
601+
602+ {
603+ "INVITE with no offer, with 100rel, with Multipart",
604+ PJSIP_INV_REQUIRE_100REL,
605+ PJ_TRUE,
606+ 1,
607+ { OFFERER_UAS },
608+ PJ_TRUE
609 },
610 #endif
611
612@@ -584,14 +653,24 @@ static inv_test_param_t test_params[] =
613 0,
614 PJ_TRUE,
615 2,
616- { OFFERER_UAC, OFFERER_UAC }
617+ { OFFERER_UAC, OFFERER_UAC },
618+ PJ_FALSE
619+ },
620+ {
621+ "INVITE and UPDATE by UAC, with Multipart",
622+ 0,
623+ PJ_TRUE,
624+ 2,
625+ { OFFERER_UAC, OFFERER_UAC },
626+ PJ_TRUE
627 },
628 {
629 "INVITE and UPDATE by UAC, with 100rel",
630 PJSIP_INV_REQUIRE_100REL,
631 PJ_TRUE,
632 2,
633- { OFFERER_UAC, OFFERER_UAC }
634+ { OFFERER_UAC, OFFERER_UAC },
635+ PJ_FALSE
636 },
637 #endif
638
639@@ -617,6 +696,14 @@ static inv_test_param_t test_params[] =
640 4,
641 { OFFERER_UAC, OFFERER_UAS, OFFERER_UAC, OFFERER_UAS }
642 },
643+ {
644+ "INVITE and many UPDATE by UAC and UAS, with Multipart",
645+ 0,
646+ PJ_TRUE,
647+ 4,
648+ { OFFERER_UAC, OFFERER_UAS, OFFERER_UAC, OFFERER_UAS },
649+ PJ_TRUE
650+ },
651
652 };
653