ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/external/management/libs/pjproject/patches/0001-change_linker.patch b/external/management/libs/pjproject/patches/0001-change_linker.patch
new file mode 100644
index 0000000..5fd1503
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0001-change_linker.patch
@@ -0,0 +1,11 @@
+--- a/build/cc-auto.mak.in
++++ b/build/cc-auto.mak.in
+@@ -2,7 +2,7 @@ export CC = @CC@ -c
+ export CXX = @CXX@ -c
+ export AR = @AR@
+ export AR_FLAGS = @AR_FLAGS@
+-export LD = @LD@
++export LD = @CXX@
+ export LDOUT = -o 
+ export RANLIB = @RANLIB@
+ 
diff --git a/external/management/libs/pjproject/patches/0002-uclibc-linker-unrecognized-options.patch b/external/management/libs/pjproject/patches/0002-uclibc-linker-unrecognized-options.patch
new file mode 100644
index 0000000..9930cef
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0002-uclibc-linker-unrecognized-options.patch
@@ -0,0 +1,11 @@
+--- a/build/rules.mak
++++ b/build/rules.mak
+@@ -13,7 +13,7 @@ SHLIB = $($(APP)_SHLIB)
+ SONAME = $($(APP)_SONAME)
+ 
+ ifeq ($(SHLIB_SUFFIX),so)
+-SHLIB_OPT := -shared -Wl,-soname,$(SHLIB)
++SHLIB_OPT := -shared
+ else ifeq ($(SHLIB_SUFFIX),dylib)
+ SHLIB_OPT := -dynamiclib -undefined dynamic_lookup -flat_namespace
+ else ifeq ($(SHLIB_SUFFIX),dll)
diff --git a/external/management/libs/pjproject/patches/0003-non-gnu-pthreads.patch b/external/management/libs/pjproject/patches/0003-non-gnu-pthreads.patch
new file mode 100644
index 0000000..b44cd70
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0003-non-gnu-pthreads.patch
@@ -0,0 +1,20 @@
+--- a/pjlib/src/pj/os_core_unix.c
++++ b/pjlib/src/pj/os_core_unix.c
+@@ -1139,7 +1139,7 @@ static pj_status_t init_mutex(pj_mutex_t
+ 	return PJ_RETURN_OS_ERROR(rc);
+ 
+     if (type == PJ_MUTEX_SIMPLE) {
+-#if (defined(PJ_LINUX) && PJ_LINUX!=0) || \
++#if (defined(PJ_LINUX) && PJ_LINUX!=0 && defined(__GLIBC__)) || \
+     defined(PJ_HAS_PTHREAD_MUTEXATTR_SETTYPE)
+ 	rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
+ #elif (defined(PJ_RTEMS) && PJ_RTEMS!=0) || \
+@@ -1149,7 +1149,7 @@ static pj_status_t init_mutex(pj_mutex_t
+ 	rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
+ #endif
+     } else {
+-#if (defined(PJ_LINUX) && PJ_LINUX!=0) || \
++#if (defined(PJ_LINUX) && PJ_LINUX!=0 && defined(__GLIBC__)) || \
+      defined(PJ_HAS_PTHREAD_MUTEXATTR_SETTYPE)
+ 	rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ #elif (defined(PJ_RTEMS) && PJ_RTEMS!=0) || \
diff --git a/external/management/libs/pjproject/patches/0004-config_site.patch b/external/management/libs/pjproject/patches/0004-config_site.patch
new file mode 100644
index 0000000..b5e6d86
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0004-config_site.patch
@@ -0,0 +1,82 @@
+--- /dev/null
++++ b/pjlib/include/pj/config_site.h
+@@ -0,0 +1,79 @@
++/*
++ * Asterisk config_site.h
++ */
++
++#include <sys/select.h>
++
++/*
++ * Defining PJMEDIA_HAS_SRTP to 0 does NOT disable Asterisk's ability to use srtp.
++ * It only disables the pjmedia srtp transport which Asterisk doesn't use.
++ * The reason for the disable is that while Asterisk works fine with older libsrtp
++ * versions, newer versions of pjproject won't compile with them.
++ */
++#define PJMEDIA_HAS_SRTP 0
++
++#define PJ_HAS_IPV6 1
++#define NDEBUG 1
++
++#define PJ_MAX_HOSTNAME (256)
++#define PJSIP_MAX_URL_SIZE (512)
++#ifdef PJ_HAS_LINUX_EPOLL
++#define PJ_IOQUEUE_MAX_HANDLES	(5000)
++#else
++#define PJ_IOQUEUE_MAX_HANDLES	(FD_SETSIZE)
++#endif
++#define PJ_IOQUEUE_HAS_SAFE_UNREG 1
++#define PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL (16)
++
++#define PJ_SCANNER_USE_BITWISE	0
++#define PJ_OS_HAS_CHECK_STACK	0
++
++#ifndef PJ_LOG_MAX_LEVEL
++#define PJ_LOG_MAX_LEVEL		6
++#endif
++
++#define PJ_ENABLE_EXTRA_CHECK	1
++#define PJSIP_MAX_TSX_COUNT		((64*1024)-1)
++#define PJSIP_MAX_DIALOG_COUNT	((64*1024)-1)
++#define PJSIP_UDP_SO_SNDBUF_SIZE	(512*1024)
++#define PJSIP_UDP_SO_RCVBUF_SIZE	(512*1024)
++#define PJ_DEBUG			0
++#define PJSIP_SAFE_MODULE		0
++#define PJ_HAS_STRICMP_ALNUM		0
++
++/*
++ * Do not ever enable PJ_HASH_USE_OWN_TOLOWER because the algorithm is
++ * inconsistently used when calculating the hash value and doesn't
++ * convert the same characters as pj_tolower()/tolower().  Thus you
++ * can get different hash values if the string hashed has certain
++ * characters in it.  (ASCII '@', '[', '\\', ']', '^', and '_')
++ */
++#undef PJ_HASH_USE_OWN_TOLOWER
++
++/*
++  It is imperative that PJSIP_UNESCAPE_IN_PLACE remain 0 or undefined.
++  Enabling it will result in SEGFAULTS when URIs containing escape sequences are encountered.
++*/
++#undef PJSIP_UNESCAPE_IN_PLACE
++#define PJSIP_MAX_PKT_LEN			65535
++
++#undef PJ_TODO
++#define PJ_TODO(x)
++
++/* Defaults too low for WebRTC */
++#define PJ_ICE_MAX_CAND 64
++#define PJ_ICE_MAX_CHECKS (PJ_ICE_MAX_CAND * PJ_ICE_MAX_CAND)
++
++/* Increase limits to allow more formats */
++#define	PJMEDIA_MAX_SDP_FMT   64
++#define	PJMEDIA_MAX_SDP_BANDW   4
++#define	PJMEDIA_MAX_SDP_ATTR   (PJMEDIA_MAX_SDP_FMT*2 + 4)
++#define	PJMEDIA_MAX_SDP_MEDIA   16
++
++/*
++ * Turn off the periodic sending of CRLNCRLN.  Default is on (90 seconds),
++ * which conflicts with the global section's keep_alive_interval option in
++ * pjsip.conf.
++ */
++#define PJSIP_TCP_KEEP_ALIVE_INTERVAL	0
++#define PJSIP_TLS_KEEP_ALIVE_INTERVAL	0
diff --git a/external/management/libs/pjproject/patches/0005-remove-hardcoded-lstdc++.patch b/external/management/libs/pjproject/patches/0005-remove-hardcoded-lstdc++.patch
new file mode 100644
index 0000000..d1db8da
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0005-remove-hardcoded-lstdc++.patch
@@ -0,0 +1,22 @@
+--- a/build.mak.in
++++ b/build.mak.in
+@@ -296,7 +296,6 @@ export APP_LDLIBS := $(PJSUA_LIB_LDLIB)
+ 	$(PJLIB_LDLIB) \
+ 	@LIBS@
+ export APP_LDXXLIBS := $(PJSUA2_LIB_LDLIB) \
+-	-lstdc++ \
+ 	$(APP_LDLIBS)
+ 
+ # Here are the variabels to use if application is using the library
+--- a/pjsip/build/Makefile
++++ b/pjsip/build/Makefile
+@@ -152,8 +152,7 @@ export PJSUA2_LIB_LDFLAGS += $(PJSUA_LIB
+ 			     $(PJNATH_LDLIB) \
+ 			     $(PJLIB_UTIL_LDLIB) \
+ 			     $(PJLIB_LDLIB) \
+-			     $(_LDFLAGS) \
+-			     -lstdc++
++			     $(_LDFLAGS)
+ 
+ 
+ ###############################################################################
diff --git a/external/management/libs/pjproject/patches/0006-fix-pkg_config-file.patch b/external/management/libs/pjproject/patches/0006-fix-pkg_config-file.patch
new file mode 100644
index 0000000..b8e9391
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0006-fix-pkg_config-file.patch
@@ -0,0 +1,25 @@
+--- a/libpjproject.pc.in
++++ b/libpjproject.pc.in
+@@ -2,8 +2,8 @@
+ 
+ prefix=@PREFIX@
+ exec_prefix=${prefix}
+-libdir=@LIBDIR@
+-includedir=@INCLUDEDIR@
++libdir=${exec_prefix}/lib
++includedir=${prefix}/include
+ 
+ Name: libpjproject
+ Description: Multimedia communication library
+--- a/build.mak.in
++++ b/build.mak.in
+@@ -314,8 +314,7 @@ export PJ_LIBXX_FILES := $(APP_LIBXX_FIL
+ # And here are the variables to use if application is using the
+ # library from the install location (i.e. --prefix)
+ export PJ_INSTALL_DIR := @prefix@
+-export PJ_INSTALL_INC_DIR := @includedir@
+ export PJ_INSTALL_LIB_DIR := @libdir@
+-export PJ_INSTALL_CFLAGS := -I$(PJ_INSTALL_INC_DIR) -DPJ_AUTOCONF=1 @CFLAGS@
++export PJ_INSTALL_CFLAGS := -DPJ_AUTOCONF=1 @CFLAGS@
+ export PJ_INSTALL_CXXFLAGS := @CXXFLAGS@ $(PJ_INSTALL_CFLAGS)
+ export PJ_INSTALL_LDFLAGS := -L$(PJ_INSTALL_LIB_DIR) $(APP_LDLIBS)
diff --git a/external/management/libs/pjproject/patches/0011-sip_inv_patch.patch b/external/management/libs/pjproject/patches/0011-sip_inv_patch.patch
new file mode 100644
index 0000000..c410b9a
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0011-sip_inv_patch.patch
@@ -0,0 +1,37 @@
+commit c3c1bf45cae2a35003aa16c267d59f97027f9c5e
+Author: Kevin Harwell <kharwell@digium.com>
+Date:   Thu Jun 11 11:11:13 2020 -0500
+
+    sip_inv - fix invite session ref count crash
+    
+    Ensure the session's ref count is only decremented under proper conditons.
+    
+    For more details see the following issue report:
+    https://github.com/pjsip/pjproject/issues/2443
+    
+    Patch supplied by sauwming
+
+--- a/pjsip/src/pjsip-ua/sip_inv.c
++++ b/pjsip/src/pjsip-ua/sip_inv.c
+@@ -323,9 +323,19 @@ static void inv_set_state(pjsip_inv_sess
+ 	(*mod_inv.cb.on_state_changed)(inv, e);
+     pjsip_inv_dec_ref(inv);
+ 
+-    /* Only decrement when previous state is not already DISCONNECTED */
++    /* The above callback may change the state, so we need to be careful here
++     * and only decrement inv under the following conditions:
++     * 1. If the state parameter is DISCONNECTED, and previous state is not
++     *    already DISCONNECTED.
++     *    This is to make sure that dec_ref() is not called more than once.
++     * 2. If current state is PJSIP_INV_STATE_DISCONNECTED.
++     *    This is to make sure that dec_ref() is not called if user restarts
++     *    inv within the callback. Note that this check must be last since
++     *    inv may have already been destroyed.
++     */
+     if (state == PJSIP_INV_STATE_DISCONNECTED &&
+-	prev_state != PJSIP_INV_STATE_DISCONNECTED) 
++	prev_state != PJSIP_INV_STATE_DISCONNECTED &&
++	inv->state == PJSIP_INV_STATE_DISCONNECTED) 
+     {
+ 	pjsip_inv_dec_ref(inv);
+     }
diff --git a/external/management/libs/pjproject/patches/0020-pjlib_cancel_timer_0.patch b/external/management/libs/pjproject/patches/0020-pjlib_cancel_timer_0.patch
new file mode 100644
index 0000000..3fe6294
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0020-pjlib_cancel_timer_0.patch
@@ -0,0 +1,35 @@
+commit 40dd48d10911f4ff9b8dfbf16428fbc9acc434ba
+Author: Riza Sulistyo <trengginas@users.noreply.github.com>
+Date:   Thu Jul 9 17:47:24 2020 +0700
+
+    Modify timer_id check on cancel() (#2463)
+    
+    * modify timer_id check on cancel().
+    
+    * modification based on comments.
+
+--- a/pjlib/include/pj/timer.h
++++ b/pjlib/include/pj/timer.h
+@@ -120,7 +120,10 @@ typedef struct pj_timer_entry
+ 
+     /** 
+      * Internal unique timer ID, which is assigned by the timer heap. 
+-     * Application should not touch this ID.
++     * Positive values indicate that the timer entry is running, 
++     * while -1 means that it's not. Any other value may indicate that it 
++     * hasn't been properly initialised or is in a bad state.
++     * Application should not touch this ID. 
+      */
+     pj_timer_id_t _timer_id;
+ 
+--- a/pjlib/src/pj/timer.c
++++ b/pjlib/src/pj/timer.c
+@@ -535,7 +535,7 @@ static int cancel( pj_timer_heap_t *ht,
+     PJ_CHECK_STACK();
+ 
+     // Check to see if the timer_id is out of range
+-    if (entry->_timer_id < 0 || (pj_size_t)entry->_timer_id > ht->max_size) {
++    if (entry->_timer_id < 1 || (pj_size_t)entry->_timer_id >= ht->max_size) {
+ 	entry->_timer_id = -1;
+     	return 0;
+     }
diff --git a/external/management/libs/pjproject/patches/0050-fix-race-parallel-build.patch b/external/management/libs/pjproject/patches/0050-fix-race-parallel-build.patch
new file mode 100644
index 0000000..3a48d41
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0050-fix-race-parallel-build.patch
@@ -0,0 +1,68 @@
+From 78683646c8bc670ec730a42494e075f671a08e28 Mon Sep 17 00:00:00 2001
+From: Guido Falsi <mad@madpilot.net>
+Date: Mon, 11 May 2020 08:50:39 +0200
+Subject: [PATCH] Fix race condition in parallel builds (#2426)
+
+* Some targets residing in `OBJDIRS` are missing a dependency on that directory, which results in a race condition, causing build to fail sometimes due to the directory not existing when running parallel builds.
+
+* The `PJSUA_LIB` variable is not defined anywhere, resulting in an empty value, and no correct dependency on the pjsua shared library for `pjsua2`. The correct variable seems to be `PJSUA_LIB_LIB`, defined at the start of this same `Makefile`.
+---
+ build/rules.mak      | 12 ++++++------
+ pjsip/build/Makefile |  2 +-
+ 2 files changed, 7 insertions(+), 7 deletions(-)
+
+--- a/build/rules.mak
++++ b/build/rules.mak
+@@ -129,7 +129,7 @@ endif
+ $(OBJDIR)/$(app).o: $(OBJDIRS) $(OBJS)
+ 	$(CROSS_COMPILE)ld -r -o $@ $(OBJS)
+ 
+-$(OBJDIR)/$(app).ko: $(OBJDIR)/$(app).o
++$(OBJDIR)/$(app).ko: $(OBJDIR)/$(app).o | $(OBJDIRS)
+ 	@echo Creating kbuild Makefile...
+ 	@echo "# Our module name:" > $(OBJDIR)/Makefile
+ 	@echo 'obj-m += $(app).o' >> $(OBJDIR)/Makefile
+@@ -154,27 +154,27 @@ $(OBJDIR)/$(app).ko: $(OBJDIR)/$(app).o
+ ../lib/$(app).ko: $(LIB) $(OBJDIR)/$(app).ko
+ 	cp $(OBJDIR)/$(app).ko ../lib
+ 
+-$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.m
++$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.m | $(OBJDIRS)
+ 	$(CC) $($(APP)_CFLAGS) \
+ 		$(CC_OUT)$(subst /,$(HOST_PSEP),$@) \
+ 		$(subst /,$(HOST_PSEP),$<) 
+ 
+-$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.c
++$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.c | $(OBJDIRS)
+ 	$(CC) $($(APP)_CFLAGS) \
+ 		$(CC_OUT)$(subst /,$(HOST_PSEP),$@) \
+ 		$(subst /,$(HOST_PSEP),$<) 
+ 
+-$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.S
++$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.S | $(OBJDIRS)
+ 	$(CC) $($(APP)_CFLAGS) \
+ 		$(CC_OUT)$(subst /,$(HOST_PSEP),$@) \
+ 		$(subst /,$(HOST_PSEP),$<) 
+ 
+-$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.cpp
++$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.cpp | $(OBJDIRS)
+ 	$(CXX) $($(APP)_CXXFLAGS) \
+ 		$(CC_OUT)$(subst /,$(HOST_PSEP),$@) \
+ 		$(subst /,$(HOST_PSEP),$<)
+ 
+-$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.cc
++$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.cc | $(OBJDIRS)
+ 	$(CXX) $($(APP)_CXXFLAGS) \
+ 		$(CC_OUT)$(subst /,$(HOST_PSEP),$@) \
+ 		$(subst /,$(HOST_PSEP),$<)
+--- a/pjsip/build/Makefile
++++ b/pjsip/build/Makefile
+@@ -261,7 +261,7 @@ $(PJSUA_LIB_LIB) $(PJSUA_LIB_SONAME): $(
+ 
+ pjsua2-lib: $(PJSUA2_LIB_LIB)
+ $(PJSUA2_LIB_SONAME): $(PJSUA2_LIB_LIB)
+-$(PJSUA2_LIB_LIB) $(PJSUA2_LIB_SONAME): $(PJSUA_LIB) $(PSJUA_LIB_SONAME) $(PJSIP_LIB) $(PJSIP_SONAME) $(PJSIP_SIMPLE_LIB) $(PJSIP_SIMPLE_SONAME) $(PJSIP_UA_LIB) $(PJSIP_UA_SONAME)
++$(PJSUA2_LIB_LIB) $(PJSUA2_LIB_SONAME): $(PJSUA_LIB_LIB) $(PJSUA_LIB_SONAME) $(PJSIP_LIB) $(PJSIP_SONAME) $(PJSIP_SIMPLE_LIB) $(PJSIP_SIMPLE_SONAME) $(PJSIP_UA_LIB) $(PJSIP_UA_SONAME)
+ 	$(MAKE) -f $(RULES_MAK) APP=PJSUA2_LIB app=pjsua2-lib $(subst /,$(HOST_PSEP),$(LIBDIR)/$@)
+ 
+ pjsip-test: $(TEST_EXE)
diff --git a/external/management/libs/pjproject/patches/0060-clone-sdp-for-sip-timer-refresh-invite.patch b/external/management/libs/pjproject/patches/0060-clone-sdp-for-sip-timer-refresh-invite.patch
new file mode 100644
index 0000000..0fda06d
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0060-clone-sdp-for-sip-timer-refresh-invite.patch
@@ -0,0 +1,27 @@
+--- a/pjmedia/src/pjmedia/sdp_neg.c
++++ b/pjmedia/src/pjmedia/sdp_neg.c
+@@ -906,7 +906,7 @@ static pj_status_t process_m_answer( pj_
+  * after receiving remote answer.
+  */
+ static pj_status_t process_answer(pj_pool_t *pool,
+-				  pjmedia_sdp_session *offer,
++				  pjmedia_sdp_session *local_offer,
+ 				  pjmedia_sdp_session *answer,
+ 				  pj_bool_t allow_asym,
+ 				  pjmedia_sdp_session **p_active)
+@@ -914,10 +914,14 @@ static pj_status_t process_answer(pj_poo
+     unsigned omi = 0; /* Offer media index */
+     unsigned ami = 0; /* Answer media index */
+     pj_bool_t has_active = PJ_FALSE;
++    pjmedia_sdp_session *offer;
+     pj_status_t status;
+ 
+     /* Check arguments. */
+-    PJ_ASSERT_RETURN(pool && offer && answer && p_active, PJ_EINVAL);
++    PJ_ASSERT_RETURN(pool && local_offer && answer && p_active, PJ_EINVAL);
++
++    /* Duplicate local offer SDP. */
++    offer = pjmedia_sdp_session_clone(pool, local_offer);
+ 
+     /* Check that media count match between offer and answer */
+     // Ticket #527, different media count is allowed for more interoperability,
diff --git a/external/management/libs/pjproject/patches/0070-fix-incorrect-copying-when-creating-cancel.patch b/external/management/libs/pjproject/patches/0070-fix-incorrect-copying-when-creating-cancel.patch
new file mode 100644
index 0000000..c8e4b4c
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0070-fix-incorrect-copying-when-creating-cancel.patch
@@ -0,0 +1,32 @@
+From ce18018cc17bef8f80c08686e3a7b28384ef3ba5 Mon Sep 17 00:00:00 2001
+From: sauwming <ming@teluu.com>
+Date: Mon, 12 Oct 2020 13:31:25 +0800
+Subject: [PATCH] Fix incorrect copying of destination info when creating
+ CANCEL (#2546)
+
+---
+ pjsip/src/pjsip/sip_util.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+--- a/pjsip/src/pjsip/sip_util.c
++++ b/pjsip/src/pjsip/sip_util.c
+@@ -779,14 +779,14 @@ PJ_DEF(pj_status_t) pjsip_endpt_create_c
+ 	    pjsip_hdr_clone(cancel_tdata->pool, req_tdata->saved_strict_route);
+     }
+ 
+-    /* Copy the destination host name from the original request */
+-    pj_strdup(cancel_tdata->pool, &cancel_tdata->dest_info.name,
+-	      &req_tdata->dest_info.name);
+-
+-    /* Finally copy the destination info from the original request */
++    /* Copy the destination info from the original request */
+     pj_memcpy(&cancel_tdata->dest_info, &req_tdata->dest_info,
+ 	      sizeof(req_tdata->dest_info));
+ 
++    /* Finally, copy the destination host name from the original request */
++    pj_strdup(cancel_tdata->pool, &cancel_tdata->dest_info.name,
++	      &req_tdata->dest_info.name);
++
+     /* Done.
+      * Return the transmit buffer containing the CANCEL request.
+      */
diff --git a/external/management/libs/pjproject/patches/0080-fix-sdp-neg-modify-local-offer.patch b/external/management/libs/pjproject/patches/0080-fix-sdp-neg-modify-local-offer.patch
new file mode 100644
index 0000000..889d012
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0080-fix-sdp-neg-modify-local-offer.patch
@@ -0,0 +1,31 @@
+--- a/pjmedia/src/pjmedia/sdp_neg.c
++++ b/pjmedia/src/pjmedia/sdp_neg.c
+@@ -304,7 +304,6 @@ PJ_DEF(pj_status_t) pjmedia_sdp_neg_modi
+ {
+     pjmedia_sdp_session *new_offer;
+     pjmedia_sdp_session *old_offer;
+-    char media_used[PJMEDIA_MAX_SDP_MEDIA];
+     unsigned oi; /* old offer media index */
+     pj_status_t status;
+ 
+@@ -323,8 +322,19 @@ PJ_DEF(pj_status_t) pjmedia_sdp_neg_modi
+     /* Change state to STATE_LOCAL_OFFER */
+     neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;
+ 
++    /* When there is no active local SDP in state PJMEDIA_SDP_NEG_STATE_DONE,
++     * it means that the previous initial SDP nego must have been failed,
++     * so we'll just set the local SDP offer here.
++     */
++    if (!neg->active_local_sdp) {
++	neg->initial_sdp_tmp = NULL;
++	neg->initial_sdp = pjmedia_sdp_session_clone(pool, local);
++	neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local);
++
++	return PJ_SUCCESS;
++    }
++
+     /* Init vars */
+-    pj_bzero(media_used, sizeof(media_used));
+     old_offer = neg->active_local_sdp;
+     new_offer = pjmedia_sdp_session_clone(pool, local);
+ 
diff --git a/external/management/libs/pjproject/patches/0090-Skip-unsupported-digest-algorithm-2408.patch b/external/management/libs/pjproject/patches/0090-Skip-unsupported-digest-algorithm-2408.patch
new file mode 100644
index 0000000..c779f67
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0090-Skip-unsupported-digest-algorithm-2408.patch
@@ -0,0 +1,201 @@
+From bdbeb7c4b2b11efc2e59f5dee7aa4360a2bc9fff Mon Sep 17 00:00:00 2001
+From: sauwming <ming@teluu.com>
+Date: Thu, 22 Apr 2021 14:03:28 +0800
+Subject: [PATCH 90/90] Skip unsupported digest algorithm (#2408)
+
+Co-authored-by: Nanang Izzuddin <nanang@teluu.com>
+---
+ pjsip/src/pjsip/sip_auth_client.c             | 32 +++++--
+ tests/pjsua/scripts-sipp/uas-auth-two-algo.py |  7 ++
+ .../pjsua/scripts-sipp/uas-auth-two-algo.xml  | 83 +++++++++++++++++++
+ 3 files changed, 117 insertions(+), 5 deletions(-)
+ create mode 100644 tests/pjsua/scripts-sipp/uas-auth-two-algo.py
+ create mode 100644 tests/pjsua/scripts-sipp/uas-auth-two-algo.xml
+
+--- a/pjsip/src/pjsip/sip_auth_client.c
++++ b/pjsip/src/pjsip/sip_auth_client.c
+@@ -1042,7 +1042,7 @@ static pj_status_t process_auth( pj_pool
+     pjsip_hdr *hdr;
+     pj_status_t status;
+ 
+-    /* See if we have sent authorization header for this realm */
++    /* See if we have sent authorization header for this realm (and scheme) */
+     hdr = tdata->msg->hdr.next;
+     while (hdr != &tdata->msg->hdr) {
+ 	if ((hchal->type == PJSIP_H_WWW_AUTHENTICATE &&
+@@ -1052,7 +1052,8 @@ static pj_status_t process_auth( pj_pool
+ 	{
+ 	    sent_auth = (pjsip_authorization_hdr*) hdr;
+ 	    if (pj_stricmp(&hchal->challenge.common.realm,
+-			   &sent_auth->credential.common.realm )==0)
++			   &sent_auth->credential.common.realm)==0 &&
++		pj_stricmp(&hchal->scheme, &sent_auth->scheme)==0)
+ 	    {
+ 		/* If this authorization has empty response, remove it. */
+ 		if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 &&
+@@ -1062,6 +1063,14 @@ static pj_status_t process_auth( pj_pool
+ 		    hdr = hdr->next;
+ 		    pj_list_erase(sent_auth);
+ 		    continue;
++		} else
++		if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 &&
++		    pj_stricmp(&sent_auth->credential.digest.algorithm,
++		               &hchal->challenge.digest.algorithm)!=0)
++		{
++		    /* Same 'digest' scheme but different algo */
++		    hdr = hdr->next;
++		    continue;
+ 		} else {
+ 		    /* Found previous authorization attempt */
+ 		    break;
+@@ -1155,9 +1164,10 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reini
+ {
+     pjsip_tx_data *tdata;
+     const pjsip_hdr *hdr;
+-    unsigned chal_cnt;
++    unsigned chal_cnt, auth_cnt;
+     pjsip_via_hdr *via;
+     pj_status_t status;
++    pj_status_t last_auth_err;
+ 
+     PJ_ASSERT_RETURN(sess && rdata && old_request && new_request,
+ 		     PJ_EINVAL);
+@@ -1178,6 +1188,8 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reini
+      */
+     hdr = rdata->msg_info.msg->hdr.next;
+     chal_cnt = 0;
++    auth_cnt = 0;
++    last_auth_err = PJSIP_EAUTHNOAUTH;
+     while (hdr != &rdata->msg_info.msg->hdr) {
+ 	pjsip_cached_auth *cached_auth;
+ 	const pjsip_www_authenticate_hdr *hchal;
+@@ -1222,8 +1234,13 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reini
+ 	 */
+ 	status = process_auth(tdata->pool, hchal, tdata->msg->line.req.uri,
+ 			      tdata, sess, cached_auth, &hauth);
+-	if (status != PJ_SUCCESS)
+-	    return status;
++	if (status != PJ_SUCCESS) {
++	    last_auth_err = status;
++
++	    /* Process next header. */
++	    hdr = hdr->next;
++	    continue;
++	}
+ 
+ 	if (pj_pool_get_used_size(cached_auth->pool) >
+ 	    PJSIP_AUTH_CACHED_POOL_MAX_SIZE) 
+@@ -1236,12 +1253,17 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reini
+ 
+ 	/* Process next header. */
+ 	hdr = hdr->next;
++	auth_cnt++;
+     }
+ 
+     /* Check if challenge is present */
+     if (chal_cnt == 0)
+ 	return PJSIP_EAUTHNOCHAL;
+ 
++    /* Check if any authorization header has been created */
++    if (auth_cnt == 0)
++	return last_auth_err;
++
+     /* Remove branch param in Via header. */
+     via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+     via->branch_param.slen = 0;
+--- /dev/null
++++ b/tests/pjsua/scripts-sipp/uas-auth-two-algo.py
+@@ -0,0 +1,7 @@
++# $Id$
++#
++import inc_const as const
++
++PJSUA = ["--null-audio --max-calls=1 --id=sip:a@localhost --username=a --realm=* --registrar=$SIPP_URI"]
++
++PJSUA_EXPECTS = [[0, "registration success", ""]]
+--- /dev/null
++++ b/tests/pjsua/scripts-sipp/uas-auth-two-algo.xml
+@@ -0,0 +1,83 @@
++<?xml version="1.0" encoding="ISO-8859-1" ?>
++<!DOCTYPE scenario SYSTEM "sipp.dtd">
++
++<scenario name="Basic UAS responder">
++  <recv request="REGISTER" crlf="true">
++  </recv>
++
++  <send>
++    <![CDATA[
++      SIP/2.0 100 Trying
++      [last_Via:];received=1.1.1.1;rport=1111
++      [last_From:]
++      [last_To:];tag=[call_number]
++      [last_Call-ID:]
++      [last_CSeq:]
++      Content-Length: 0
++    ]]>
++  </send>
++
++  <send>
++    <![CDATA[
++      SIP/2.0 401 Unauthorized
++      [last_Via:];received=1.1.1.1;rport=1111
++      [last_From:]
++      [last_To:];tag=[call_number]
++      [last_Call-ID:]
++      [last_CSeq:]
++      WWW-Authenticate: Digest realm="sip.linphone.org", nonce="PARV4gAAAADgw3asAADW8zsi5BEAAAAA", opaque="+GNywA==", algorithm=SHA-256, qop="auth"
++      WWW-Authenticate: Digest realm="sip.linphone.org", nonce="PARV4gAAAADgw3asAADW8zsi5BEAAAAA", opaque="+GNywA==", algorithm=MD5, qop="auth"
++      WWW-Authenticate: Digest realm="sip.linphone.org", nonce="PARV4gAAAADgw3asAADW8zsi5BEAAAAA", opaque="+GNywA==", algorithm=MD2, qop="auth"
++      Content-Length: 0
++    ]]>
++  </send>
++
++  <recv request="REGISTER" crlf="true">
++    <action>
++      <ereg regexp=".*"
++            search_in="hdr"
++	    header="Authorization:"
++	    assign_to="have_auth" />
++    </action>
++  </recv>
++
++  <nop next="resp_okay" test="have_auth" />
++  
++  <send next="end">
++    <![CDATA[
++      SIP/2.0 403 no auth
++      [last_Via:];received=1.1.1.1;rport=1111
++      [last_From:]
++      [last_To:];tag=[call_number]
++      [last_Call-ID:]
++      [last_CSeq:]
++      [last_Contact:]
++      Content-Length: 0
++    ]]>
++  </send>
++
++  <label id="resp_okay" />
++  
++  <send>
++    <![CDATA[
++      SIP/2.0 200 OK
++      [last_Via:];received=1.1.1.1;rport=1111
++      [last_From:]
++      [last_To:];tag=[call_number]
++      [last_Call-ID:]
++      [last_CSeq:]
++      [last_Contact:]
++      Content-Length: 0
++    ]]>
++  </send>
++
++  <label id="end" />
++
++  <!-- definition of the response time repartition table (unit is ms)   -->
++  <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
++
++  <!-- definition of the call length repartition table (unit is ms)     -->
++  <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
++
++</scenario>
++
diff --git a/external/management/libs/pjproject/patches/0100-fix-double-stun-free.patch b/external/management/libs/pjproject/patches/0100-fix-double-stun-free.patch
new file mode 100644
index 0000000..f1691b9
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0100-fix-double-stun-free.patch
@@ -0,0 +1,78 @@
+commit f0ff5817d0647bdecd1ec99488db9378e304cf83
+Author: sauwming <ming@teluu.com>
+Date:   Mon May 17 09:56:27 2021 +0800
+
+    Fix double free of stun session (#2709)
+
+--- a/pjnath/include/pjnath/stun_session.h
++++ b/pjnath/include/pjnath/stun_session.h
+@@ -341,6 +341,7 @@ struct pj_stun_tx_data
+     pj_pool_t		*pool;		/**< Pool.			    */
+     pj_stun_session	*sess;		/**< The STUN session.		    */
+     pj_stun_msg		*msg;		/**< The STUN message.		    */
++    pj_bool_t		 is_destroying; /**< Is destroying?		    */
+ 
+     void		*token;		/**< The token.			    */
+ 
+--- a/pjnath/src/pjnath/stun_session.c
++++ b/pjnath/src/pjnath/stun_session.c
+@@ -167,16 +167,27 @@ static void tdata_on_destroy(void *arg)
+ {
+     pj_stun_tx_data *tdata = (pj_stun_tx_data*)arg;
+ 
++    if (tdata->grp_lock) {
++	pj_grp_lock_dec_ref(tdata->sess->grp_lock);
++    }
++
+     pj_pool_safe_release(&tdata->pool);
+ }
+ 
+ static void destroy_tdata(pj_stun_tx_data *tdata, pj_bool_t force)
+ {
+-    TRACE_((THIS_FILE, "tdata %p destroy request, force=%d, tsx=%p", tdata,
+-	    force, tdata->client_tsx));
++    TRACE_((THIS_FILE,
++	    "tdata %p destroy request, force=%d, tsx=%p, destroying=%d",
++	    tdata, force, tdata->client_tsx, tdata->is_destroying));
++
++    /* Just return if destroy has been requested before */
++    if (tdata->is_destroying)
++	return;
+ 
+     /* STUN session may have been destroyed, except when tdata is cached. */
+ 
++    tdata->is_destroying = PJ_TRUE;
++
+     if (tdata->res_timer.id != PJ_FALSE) {
+ 	pj_timer_heap_cancel_if_active(tdata->sess->cfg->timer_heap,
+ 				       &tdata->res_timer, PJ_FALSE);
+@@ -189,7 +200,6 @@ static void destroy_tdata(pj_stun_tx_dat
+ 	    pj_stun_client_tsx_set_data(tdata->client_tsx, NULL);
+ 	}
+ 	if (tdata->grp_lock) {
+-	    pj_grp_lock_dec_ref(tdata->sess->grp_lock);
+ 	    pj_grp_lock_dec_ref(tdata->grp_lock);
+ 	} else {
+ 	    tdata_on_destroy(tdata);
+@@ -200,11 +210,11 @@ static void destroy_tdata(pj_stun_tx_dat
+ 	    /* "Probably" this is to absorb retransmission */
+ 	    pj_time_val delay = {0, 300};
+ 	    pj_stun_client_tsx_schedule_destroy(tdata->client_tsx, &delay);
++	    tdata->is_destroying = PJ_FALSE;
+ 
+ 	} else {
+ 	    pj_list_erase(tdata);
+ 	    if (tdata->grp_lock) {
+-		pj_grp_lock_dec_ref(tdata->sess->grp_lock);
+ 		pj_grp_lock_dec_ref(tdata->grp_lock);
+ 	    } else {
+ 		tdata_on_destroy(tdata);
+@@ -238,7 +248,7 @@ static void on_cache_timeout(pj_timer_he
+     sess = tdata->sess;
+ 
+     pj_grp_lock_acquire(sess->grp_lock);
+-    if (sess->is_destroying) {
++    if (sess->is_destroying || tdata->is_destroying) {
+ 	pj_grp_lock_release(sess->grp_lock);
+ 	return;
+     }
diff --git a/external/management/libs/pjproject/patches/0110-tls-parent-listener-destroyed.patch b/external/management/libs/pjproject/patches/0110-tls-parent-listener-destroyed.patch
new file mode 100644
index 0000000..e496fe4
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0110-tls-parent-listener-destroyed.patch
@@ -0,0 +1,162 @@
+From bb92c97ea512aa0ef316c9b2335c7d57b84dfc9a Mon Sep 17 00:00:00 2001
+From: Nanang Izzuddin <nanang@teluu.com>
+Date: Wed, 16 Jun 2021 12:12:35 +0700
+Subject: [PATCH 1/2] - Avoid SSL socket parent/listener getting destroyed
+ during handshake by increasing parent's reference count. - Add missing SSL
+ socket close when the newly accepted SSL socket is discarded in SIP TLS
+ transport.
+
+---
+ pjlib/src/pj/ssl_sock_imp_common.c  | 44 +++++++++++++++++++++--------
+ pjsip/src/pjsip/sip_transport_tls.c | 23 ++++++++++++++-
+ 2 files changed, 55 insertions(+), 12 deletions(-)
+
+--- a/pjlib/src/pj/ssl_sock_imp_common.c
++++ b/pjlib/src/pj/ssl_sock_imp_common.c
+@@ -224,6 +224,8 @@ static pj_bool_t on_handshake_complete(p
+ 
+     /* Accepting */
+     if (ssock->is_server) {
++	pj_bool_t ret = PJ_TRUE;
++
+ 	if (status != PJ_SUCCESS) {
+ 	    /* Handshake failed in accepting, destroy our self silently. */
+ 
+@@ -241,6 +243,12 @@ static pj_bool_t on_handshake_complete(p
+ 		      status);
+ 	    }
+ 
++	    /* Decrement ref count of parent */
++	    if (ssock->parent->param.grp_lock) {
++		pj_grp_lock_dec_ref(ssock->parent->param.grp_lock);
++		ssock->parent = NULL;
++	    }
++
+ 	    /* Originally, this is a workaround for ticket #985. However,
+ 	     * a race condition may occur in multiple worker threads
+ 	     * environment when we are destroying SSL objects while other
+@@ -284,23 +292,29 @@ static pj_bool_t on_handshake_complete(p
+ 
+ 	    return PJ_FALSE;
+ 	}
++
+ 	/* Notify application the newly accepted SSL socket */
+ 	if (ssock->param.cb.on_accept_complete2) {
+-	    pj_bool_t ret;
+ 	    ret = (*ssock->param.cb.on_accept_complete2) 
+ 		    (ssock->parent, ssock, (pj_sockaddr_t*)&ssock->rem_addr, 
+ 		    pj_sockaddr_get_len((pj_sockaddr_t*)&ssock->rem_addr), 
+ 		    status);
+-	    if (ret == PJ_FALSE)
+-		return PJ_FALSE;	
+ 	} else if (ssock->param.cb.on_accept_complete) {
+-	    pj_bool_t ret;
+ 	    ret = (*ssock->param.cb.on_accept_complete)
+ 		      (ssock->parent, ssock, (pj_sockaddr_t*)&ssock->rem_addr,
+ 		       pj_sockaddr_get_len((pj_sockaddr_t*)&ssock->rem_addr));
+-	    if (ret == PJ_FALSE)
+-		return PJ_FALSE;
+ 	}
++
++	/* Decrement ref count of parent and reset parent (we don't need it
++	 * anymore, right?).
++	 */
++	if (ssock->parent->param.grp_lock) {
++	    pj_grp_lock_dec_ref(ssock->parent->param.grp_lock);
++	    ssock->parent = NULL;
++	}
++
++	if (ret == PJ_FALSE)
++	    return PJ_FALSE;
+     }
+ 
+     /* Connecting */
+@@ -864,9 +878,13 @@ static pj_bool_t asock_on_accept_complet
+     if (status != PJ_SUCCESS)
+ 	goto on_return;
+ 
++    /* Set parent and add ref count (avoid parent destroy during handshake) */
++    ssock->parent = ssock_parent;
++    if (ssock->parent->param.grp_lock)
++	pj_grp_lock_add_ref(ssock->parent->param.grp_lock);
++
+     /* Update new SSL socket attributes */
+     ssock->sock = newsock;
+-    ssock->parent = ssock_parent;
+     ssock->is_server = PJ_TRUE;
+     if (ssock_parent->cert) {
+ 	status = pj_ssl_sock_set_certificate(ssock, ssock->pool, 
+@@ -913,16 +931,20 @@ static pj_bool_t asock_on_accept_complet
+     ssock->asock_rbuf = (void**)pj_pool_calloc(ssock->pool, 
+ 					       ssock->param.async_cnt,
+ 					       sizeof(void*));
+-    if (!ssock->asock_rbuf)
+-        return PJ_ENOMEM;
++    if (!ssock->asock_rbuf) {
++		status = PJ_ENOMEM;
++		goto on_return;
++	}
+ 
+     for (i = 0; i<ssock->param.async_cnt; ++i) {
+-	ssock->asock_rbuf[i] = (void*) pj_pool_alloc(
++		ssock->asock_rbuf[i] = (void*) pj_pool_alloc(
+ 					    ssock->pool, 
+ 					    ssock->param.read_buffer_size + 
+ 					    sizeof(read_data_t*));
+-        if (!ssock->asock_rbuf[i])
+-            return PJ_ENOMEM;
++		if (!ssock->asock_rbuf[i]) {
++			status = PJ_ENOMEM;
++			goto on_return;
++		}
+     }
+ 
+     /* Create active socket */
+--- a/pjsip/src/pjsip/sip_transport_tls.c
++++ b/pjsip/src/pjsip/sip_transport_tls.c
+@@ -1325,9 +1325,26 @@ static pj_bool_t on_accept_complete2(pj_
+     PJ_UNUSED_ARG(src_addr_len);
+ 
+     listener = (struct tls_listener*) pj_ssl_sock_get_user_data(ssock);
++    if (!listener) {
++	/* Listener already destroyed, e.g: after TCP accept but before SSL
++	 * handshake is completed.
++	 */
++	if (new_ssock && accept_status == PJ_SUCCESS) {
++	    /* Close the SSL socket if the accept op is successful */
++	    PJ_LOG(4,(THIS_FILE,
++		      "Incoming TLS connection from %s (sock=%d) is discarded "
++		      "because listener is already destroyed",
++		      pj_sockaddr_print(src_addr, addr, sizeof(addr), 3),
++		      new_ssock));
++
++	    pj_ssl_sock_close(new_ssock);
++	}
++
++	return PJ_FALSE;
++    }
+ 
+     if (accept_status != PJ_SUCCESS) {
+-	if (listener && listener->tls_setting.on_accept_fail_cb) {
++	if (listener->tls_setting.on_accept_fail_cb) {
+ 	    pjsip_tls_on_accept_fail_param param;
+ 	    pj_ssl_sock_info ssi;
+ 
+@@ -1350,6 +1367,8 @@ static pj_bool_t on_accept_complete2(pj_
+     PJ_ASSERT_RETURN(new_ssock, PJ_TRUE);
+ 
+     if (!listener->is_registered) {
++	pj_ssl_sock_close(new_ssock);
++
+ 	if (listener->tls_setting.on_accept_fail_cb) {
+ 	    pjsip_tls_on_accept_fail_param param;
+ 	    pj_bzero(&param, sizeof(param));
+@@ -1401,6 +1420,8 @@ static pj_bool_t on_accept_complete2(pj_
+ 			 ssl_info.grp_lock, &tls);
+     
+     if (status != PJ_SUCCESS) {
++	pj_ssl_sock_close(new_ssock);
++
+ 	if (listener->tls_setting.on_accept_fail_cb) {
+ 	    pjsip_tls_on_accept_fail_param param;
+ 	    pj_bzero(&param, sizeof(param));
diff --git a/external/management/libs/pjproject/patches/0111-ssl-premature-destroy.patch b/external/management/libs/pjproject/patches/0111-ssl-premature-destroy.patch
new file mode 100644
index 0000000..3345393
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0111-ssl-premature-destroy.patch
@@ -0,0 +1,132 @@
+From 68c69f516f95df1faa42e5647e9ce7cfdc41ac38 Mon Sep 17 00:00:00 2001
+From: Nanang Izzuddin <nanang@teluu.com>
+Date: Wed, 16 Jun 2021 12:15:29 +0700
+Subject: [PATCH 2/2] - Fix silly mistake: accepted active socket created
+ without group lock in SSL socket. - Replace assertion with normal validation
+ check of SSL socket instance in OpenSSL verification callback (verify_cb())
+ to avoid crash, e.g: if somehow race condition with SSL socket destroy
+ happens or OpenSSL application data index somehow gets corrupted.
+
+---
+ pjlib/src/pj/ssl_sock_imp_common.c |  3 +-
+ pjlib/src/pj/ssl_sock_ossl.c       | 45 +++++++++++++++++++++++++-----
+ 2 files changed, 40 insertions(+), 8 deletions(-)
+
+--- a/pjlib/src/pj/ssl_sock_imp_common.c
++++ b/pjlib/src/pj/ssl_sock_imp_common.c
+@@ -949,6 +949,7 @@ static pj_bool_t asock_on_accept_complet
+ 
+     /* Create active socket */
+     pj_activesock_cfg_default(&asock_cfg);
++    asock_cfg.grp_lock = ssock->param.grp_lock;
+     asock_cfg.async_cnt = ssock->param.async_cnt;
+     asock_cfg.concurrency = ssock->param.concurrency;
+     asock_cfg.whole_data = PJ_TRUE;
+@@ -964,7 +965,7 @@ static pj_bool_t asock_on_accept_complet
+ 	    goto on_return;
+ 
+ 	pj_grp_lock_add_ref(glock);
+-	asock_cfg.grp_lock = ssock->param.grp_lock = glock;
++	ssock->param.grp_lock = glock;
+ 	pj_grp_lock_add_handler(ssock->param.grp_lock, ssock->pool, ssock,
+ 				ssl_on_destroy);
+     }
+--- a/pjlib/src/pj/ssl_sock_ossl.c
++++ b/pjlib/src/pj/ssl_sock_ossl.c
+@@ -327,7 +327,8 @@ static pj_status_t STATUS_FROM_SSL_ERR(c
+ 	ERROR_LOG("STATUS_FROM_SSL_ERR", err, ssock);
+     }
+ 
+-    ssock->last_err = err;
++    if (ssock)
++	ssock->last_err = err;
+     return GET_STATUS_FROM_SSL_ERR(err);
+ }
+ 
+@@ -344,7 +345,8 @@ static pj_status_t STATUS_FROM_SSL_ERR2(
+     /* Dig for more from OpenSSL error queue */
+     SSLLogErrors(action, ret, err, len, ssock);
+ 
+-    ssock->last_err = ssl_err;
++    if (ssock)
++	ssock->last_err = ssl_err;
+     return GET_STATUS_FROM_SSL_ERR(ssl_err);
+ }
+ 
+@@ -587,6 +589,13 @@ static pj_status_t init_openssl(void)
+ 
+     /* Create OpenSSL application data index for SSL socket */
+     sslsock_idx = SSL_get_ex_new_index(0, "SSL socket", NULL, NULL, NULL);
++	if (sslsock_idx == -1) {
++		status = STATUS_FROM_SSL_ERR2("Init", NULL, -1, ERR_get_error(), 0);
++		PJ_LOG(1,(THIS_FILE,
++				  "Fatal error: failed to get application data index for "
++				  "SSL socket"));
++		return status;
++	}
+ 
+     return status;
+ }
+@@ -614,21 +623,36 @@ static int password_cb(char *buf, int nu
+ }
+ 
+ 
+-/* SSL password callback. */
++/* SSL certificate verification result callback.
++ * Note that this callback seems to be always called from library worker
++ * thread, e.g: active socket on_read_complete callback, which should have
++ * already been equipped with race condition avoidance mechanism (should not
++ * be destroyed while callback is being invoked).
++ */
+ static int verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
+ {
+-    pj_ssl_sock_t *ssock;
+-    SSL *ossl_ssl;
++    pj_ssl_sock_t *ssock = NULL;
++    SSL *ossl_ssl = NULL;
+     int err;
+ 
+     /* Get SSL instance */
+     ossl_ssl = X509_STORE_CTX_get_ex_data(x509_ctx, 
+ 				    SSL_get_ex_data_X509_STORE_CTX_idx());
+-    pj_assert(ossl_ssl);
++    if (!ossl_ssl) {
++	PJ_LOG(1,(THIS_FILE,
++		  "SSL verification callback failed to get SSL instance"));
++	goto on_return;
++    }
+ 
+     /* Get SSL socket instance */
+     ssock = SSL_get_ex_data(ossl_ssl, sslsock_idx);
+-    pj_assert(ssock);
++    if (!ssock) {
++	/* SSL socket may have been destroyed */
++	PJ_LOG(1,(THIS_FILE,
++		  "SSL verification callback failed to get SSL socket "
++		  "instance (sslsock_idx=%d).", sslsock_idx));
++	goto on_return;
++    }
+ 
+     /* Store verification status */
+     err = X509_STORE_CTX_get_error(x509_ctx);
+@@ -706,6 +730,7 @@ static int verify_cb(int preverify_ok, X
+     if (PJ_FALSE == ssock->param.verify_peer)
+ 	preverify_ok = 1;
+ 
++on_return:
+     return preverify_ok;
+ }
+ 
+@@ -1213,6 +1238,12 @@ static void ssl_destroy(pj_ssl_sock_t *s
+ static void ssl_reset_sock_state(pj_ssl_sock_t *ssock)
+ {
+     ossl_sock_t *ossock = (ossl_sock_t *)ssock;
++
++    /* Detach from SSL instance */
++    if (ossock->ossl_ssl) {
++	SSL_set_ex_data(ossock->ossl_ssl, sslsock_idx, NULL);
++    }
++
+     /**
+      * Avoid calling SSL_shutdown() if handshake wasn't completed.
+      * OpenSSL 1.0.2f complains if SSL_shutdown() is called during an
diff --git a/external/management/libs/pjproject/patches/0120-pjmedia_sdp_attr_get_rtpmap-Strip-param-trailing-whi.patch b/external/management/libs/pjproject/patches/0120-pjmedia_sdp_attr_get_rtpmap-Strip-param-trailing-whi.patch
new file mode 100644
index 0000000..a3e668c
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0120-pjmedia_sdp_attr_get_rtpmap-Strip-param-trailing-whi.patch
@@ -0,0 +1,27 @@
+From 2ae784030b0d9cf217c3d562af20e4967f19a3dc Mon Sep 17 00:00:00 2001
+From: George Joseph <gjoseph@sangoma.com>
+Date: Tue, 14 Sep 2021 10:47:29 -0600
+Subject: [PATCH] pjmedia_sdp_attr_get_rtpmap: Strip param trailing whitespace
+
+Use pj_scan_get() to parse the param part of rtpmap so
+trailing whitespace is automatically stripped.
+
+Fixes #2827
+---
+ pjmedia/src/pjmedia/sdp.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/pjmedia/src/pjmedia/sdp.c
++++ b/pjmedia/src/pjmedia/sdp.c
+@@ -313,9 +313,9 @@ PJ_DEF(pj_status_t) pjmedia_sdp_attr_get
+ 
+ 	/* Expecting either '/' or EOF */
+ 	if (*scanner.curptr == '/') {
++	    /* Skip the '/' */
+ 	    pj_scan_get_char(&scanner);
+-	    rtpmap->param.ptr = scanner.curptr;
+-	    rtpmap->param.slen = scanner.end - scanner.curptr;
++	    pj_scan_get(&scanner, &cs_token, &rtpmap->param);
+ 	} else {
+ 	    rtpmap->param.slen = 0;
+ 	}
diff --git a/external/management/libs/pjproject/patches/0130-sip_inv-Additional-multipart-support-2919-2920.patch b/external/management/libs/pjproject/patches/0130-sip_inv-Additional-multipart-support-2919-2920.patch
new file mode 100644
index 0000000..8db8aab
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0130-sip_inv-Additional-multipart-support-2919-2920.patch
@@ -0,0 +1,653 @@
+From 0ed41eb5fd0e4192e1b7dc374f819d17aef3e805 Mon Sep 17 00:00:00 2001
+From: George Joseph <gtjoseph@users.noreply.github.com>
+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(-)
+
+--- 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;
+ 
+ 
+ /**
+@@ -1046,6 +1054,44 @@ PJ_DECL(pj_status_t) pjsip_create_sdp_bo
+ 					   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
+  * this function supports multipart message body.
+@@ -1061,6 +1107,60 @@ PJ_DECL(pj_status_t) pjsip_create_sdp_bo
+ 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
+ 
+ /**
+--- 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
+ 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,
+@@ -956,66 +958,170 @@ PJ_DEF(pj_status_t) pjsip_inv_create_uac
+     return PJ_SUCCESS;
+ }
+ 
+-PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata)
+-{
+-    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;
++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_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;
++}
++
++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;
+ 
+-	sdp_info->sdp_err = status;
++    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.
+@@ -1740,13 +1846,55 @@ PJ_DEF(pj_status_t) pjsip_create_sdp_bod
+     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);
+ 
+@@ -2069,6 +2217,7 @@ static pj_status_t inv_check_sdp_in_inco
+ 	       )
+ 	   )
+ 	{
++	    pjsip_sdp_info *tdata_sdp_info;
+ 	    const pjmedia_sdp_session *reoffer_sdp = NULL;
+ 
+ 	    PJ_LOG(4,(inv->obj_name, "Received %s response "
+@@ -2077,14 +2226,15 @@ static pj_status_t inv_check_sdp_in_inco
+ 		      (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));
+--- 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_se
+ 	    }
+ 	}
+ 
++	/* 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_
+ 					  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
+ 	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
+     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
++    },
+ 
+ };
+ 
diff --git a/external/management/libs/pjproject/patches/0140-Fix-incorrect-unescaping-of-tokens-during-parsing-29.patch b/external/management/libs/pjproject/patches/0140-Fix-incorrect-unescaping-of-tokens-during-parsing-29.patch
new file mode 100644
index 0000000..deb9d8c
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0140-Fix-incorrect-unescaping-of-tokens-during-parsing-29.patch
@@ -0,0 +1,116 @@
+From 3faf1d2b4da553bbaee04f9a13a5d084b381e5fb Mon Sep 17 00:00:00 2001
+From: sauwming <ming@teluu.com>
+Date: Tue, 4 Jan 2022 15:28:49 +0800
+Subject: [PATCH] Fix incorrect unescaping of tokens during parsing (#2933)
+
+---
+ pjsip/src/pjsip/sip_parser.c | 29 +++++++++++++++++++++++++----
+ pjsip/src/test/msg_test.c    |  6 +++---
+ 2 files changed, 28 insertions(+), 7 deletions(-)
+
+--- a/pjsip/src/pjsip/sip_parser.c
++++ b/pjsip/src/pjsip/sip_parser.c
+@@ -378,17 +378,23 @@ static pj_status_t init_parser()
+     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+     pj_cis_add_str( &pconst.pjsip_TOKEN_SPEC, TOKEN);
+ 
++    /* Token is allowed to have '%' so we do not need this. */
++    /*
+     status = pj_cis_dup(&pconst.pjsip_TOKEN_SPEC_ESC, &pconst.pjsip_TOKEN_SPEC);
+     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+     pj_cis_del_str(&pconst.pjsip_TOKEN_SPEC_ESC, "%");
++    */
+ 
+     status = pj_cis_dup(&pconst.pjsip_VIA_PARAM_SPEC, &pconst.pjsip_TOKEN_SPEC);
+     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+     pj_cis_add_str(&pconst.pjsip_VIA_PARAM_SPEC, "[:]");
+ 
++    /* Token is allowed to have '%' */
++    /*
+     status = pj_cis_dup(&pconst.pjsip_VIA_PARAM_SPEC_ESC, &pconst.pjsip_TOKEN_SPEC_ESC);
+     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+     pj_cis_add_str(&pconst.pjsip_VIA_PARAM_SPEC_ESC, "[:]");
++    */
+ 
+     status = pj_cis_dup(&pconst.pjsip_HOST_SPEC, &pconst.pjsip_ALNUM_SPEC);
+     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+@@ -1210,7 +1216,11 @@ static void parse_param_imp( pj_scanner
+ 			     unsigned option)
+ {
+     /* pname */
+-    parser_get_and_unescape(scanner, pool, spec, esc_spec, pname);
++    if (!esc_spec) {
++    	pj_scan_get(scanner, spec, pname);
++    } else {
++	parser_get_and_unescape(scanner, pool, spec, esc_spec, pname);
++    }
+ 
+     /* init pvalue */
+     pvalue->ptr = NULL;
+@@ -1240,7 +1250,12 @@ static void parse_param_imp( pj_scanner
+ 		// pj_scan_get_until_ch(scanner, ']', pvalue);
+ 		// pj_scan_get_char(scanner);
+ 	    } else if(pj_cis_match(spec, *scanner->curptr)) {
+-		parser_get_and_unescape(scanner, pool, spec, esc_spec, pvalue);
++	    	if (!esc_spec) {
++    		    pj_scan_get(scanner, spec, pvalue);
++    		} else {
++		    parser_get_and_unescape(scanner, pool, spec, esc_spec,
++		    			    pvalue);
++		}
+ 	    }
+ 	}
+     }
+@@ -1252,7 +1267,10 @@ PJ_DEF(void) pjsip_parse_param_imp(pj_sc
+ 			     	   unsigned option)
+ {
+     parse_param_imp(scanner, pool, pname, pvalue, &pconst.pjsip_TOKEN_SPEC,
+-		    &pconst.pjsip_TOKEN_SPEC_ESC, option);
++		    // Token does not need to be unescaped.
++		    // Refer to PR #2933.
++		    // &pconst.pjsip_TOKEN_SPEC_ESC,
++		    NULL, option);
+ }
+ 
+ 
+@@ -2168,7 +2186,10 @@ static void int_parse_via_param( pjsip_v
+ 	pj_scan_get_char(scanner);
+ 	parse_param_imp(scanner, pool, &pname, &pvalue,
+ 			&pconst.pjsip_VIA_PARAM_SPEC,
+-			&pconst.pjsip_VIA_PARAM_SPEC_ESC,
++		    	// Token does not need to be unescaped.
++		     	// Refer to PR #2933.
++		    	// &pconst.pjsip_VIA_PARAM_SPEC_ESC,
++			NULL,
+ 			0);
+ 
+ 	if (!parser_stricmp(pname, pconst.pjsip_BRANCH_STR) && pvalue.slen) {
+--- a/pjsip/src/test/msg_test.c
++++ b/pjsip/src/test/msg_test.c
+@@ -953,7 +953,7 @@ static int hdr_test_subject_utf(pjsip_hd
+ 
+ 
+ #define GENERIC_PARAM	     "p0=a;p1=\"ab:;cd\";p2=ab%3acd;p3"
+-#define GENERIC_PARAM_PARSED "p0=a;p1=\"ab:;cd\";p2=ab:cd;p3"
++#define GENERIC_PARAM_PARSED "p0=a;p1=\"ab:;cd\";p2=ab%3acd;p3"
+ #define PARAM_CHAR	     "][/:&+$"
+ #define SIMPLE_ADDR_SPEC     "sip:host"
+ #define ADDR_SPEC	     SIMPLE_ADDR_SPEC ";"PARAM_CHAR"="PARAM_CHAR ";p1=\";\""
+@@ -1401,7 +1401,7 @@ static int generic_param_test(pjsip_para
+     param = param->next;
+     if (pj_strcmp2(&param->name, "p2"))
+ 	return -956;
+-    if (pj_strcmp2(&param->value, "ab:cd"))
++    if (pj_strcmp2(&param->value, "ab%3acd"))
+ 	return -957;
+ 
+     param = param->next;
+@@ -1621,7 +1621,7 @@ static int hdr_test_content_type(pjsip_h
+     prm = prm->next;
+     if (prm == &hdr->media.param) return -1960;
+     if (pj_strcmp2(&prm->name, "p2")) return -1961;
+-    if (pj_strcmp2(&prm->value, "ab:cd")) return -1962;
++    if (pj_strcmp2(&prm->value, "ab%3acd")) return -1962;
+ 
+     prm = prm->next;
+     if (prm == &hdr->media.param) return -1970;
diff --git a/external/management/libs/pjproject/patches/0150-Create-generic-pjsip_hdr_find-functions.patch b/external/management/libs/pjproject/patches/0150-Create-generic-pjsip_hdr_find-functions.patch
new file mode 100644
index 0000000..5979a3b
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0150-Create-generic-pjsip_hdr_find-functions.patch
@@ -0,0 +1,169 @@
+From 7e3dfd8a15fd0f98dbf0e04d2d7a5bded90ee401 Mon Sep 17 00:00:00 2001
+From: George Joseph <gjoseph@sangoma.com>
+Date: Tue, 11 Jan 2022 09:27:23 -0700
+Subject: [PATCH] Create generic pjsip_hdr_find functions
+
+pjsip_msg_find_hdr(), pjsip_msg_find_hdr_by_name(), and
+pjsip_msg_find_hdr_by_names() require a pjsip_msg to be passed in
+so if you need to search a header list that's not in a pjsip_msg,
+you have to do it yourself.  This commit adds generic versions of
+those 3 functions that take in the actual header list head instead
+of a pjsip_msg so if you need to search a list of headers in
+something like a pjsip_multipart_part, you can do so easily.
+---
+ pjsip/include/pjsip/sip_msg.h | 53 +++++++++++++++++++++++++++++++++++
+ pjsip/src/pjsip/sip_msg.c     | 51 +++++++++++++++++++++++----------
+ 2 files changed, 89 insertions(+), 15 deletions(-)
+
+--- a/pjsip/include/pjsip/sip_msg.h
++++ b/pjsip/include/pjsip/sip_msg.h
+@@ -363,6 +363,59 @@ PJ_DECL(void*) pjsip_hdr_shallow_clone(
+ PJ_DECL(int) pjsip_hdr_print_on( void *hdr, char *buf, pj_size_t len);
+ 
+ /**
++ * Find a header in a header list by the header type.
++ *
++ * @param hdr_list  The "head" of the header list.
++ * @param type      The header type to find.
++ * @param start     The first header field where the search should begin.
++ *                  If NULL is specified, then the search will begin from the
++ *                  first header, otherwise the search will begin at the
++ *                  specified header.
++ *
++ * @return          The header field, or NULL if no header with the specified
++ *                  type is found.
++ */
++PJ_DECL(void*)  pjsip_hdr_find( const void *hdr_list,
++				pjsip_hdr_e type,
++				const void *start);
++
++/**
++ * Find a header in a header list by its name.
++ *
++ * @param hdr_list  The "head" of the header list.
++ * @param name      The header name to find.
++ * @param start     The first header field where the search should begin.
++ *                  If NULL is specified, then the search will begin from the
++ *                  first header, otherwise the search will begin at the
++ *                  specified header.
++ *
++ * @return          The header field, or NULL if no header with the specified
++ *                  type is found.
++ */
++PJ_DECL(void*)  pjsip_hdr_find_by_name( const void *hdr_list,
++					const pj_str_t *name,
++					const void *start);
++
++/**
++ * Find a header in a header list by its name and short name version.
++ *
++ * @param hdr_list  The "head" of the header list.
++ * @param name      The header name to find.
++ * @param sname     The short name version of the header name.
++ * @param start     The first header field where the search should begin.
++ *                  If NULL is specified, then the search will begin from the
++ *                  first header, otherwise the search will begin at the
++ *                  specified header.
++ *
++ * @return	    The header field, or NULL if no header with the specified
++ *		    type is found.
++ */
++PJ_DECL(void*)  pjsip_hdr_find_by_names( const void *hdr_list,
++					 const pj_str_t *name,
++					 const pj_str_t *sname,
++					 const void *start);
++
++/**
+  * @}
+  */
+ 
+--- a/pjsip/src/pjsip/sip_msg.c
++++ b/pjsip/src/pjsip/sip_msg.c
+@@ -334,13 +334,13 @@ PJ_DEF(pjsip_msg*) pjsip_msg_clone( pj_p
+     return dst;
+ }
+ 
+-PJ_DEF(void*)  pjsip_msg_find_hdr( const pjsip_msg *msg, 
+-				   pjsip_hdr_e hdr_type, const void *start)
++PJ_DEF(void*)  pjsip_hdr_find( const void *hdr_list,
++			       pjsip_hdr_e hdr_type, const void *start)
+ {
+-    const pjsip_hdr *hdr=(const pjsip_hdr*) start, *end=&msg->hdr;
++    const pjsip_hdr *hdr=(const pjsip_hdr*) start, *end=hdr_list;
+ 
+     if (hdr == NULL) {
+-	hdr = msg->hdr.next;
++	hdr = end->next;
+     }
+     for (; hdr!=end; hdr = hdr->next) {
+ 	if (hdr->type == hdr_type)
+@@ -349,14 +349,14 @@ PJ_DEF(void*)  pjsip_msg_find_hdr( const
+     return NULL;
+ }
+ 
+-PJ_DEF(void*)  pjsip_msg_find_hdr_by_name( const pjsip_msg *msg, 
+-					   const pj_str_t *name, 
+-					   const void *start)
++PJ_DEF(void*)  pjsip_hdr_find_by_name( const void *hdr_list,
++				       const pj_str_t *name,
++				       const void *start)
+ {
+-    const pjsip_hdr *hdr=(const pjsip_hdr*)start, *end=&msg->hdr;
++    const pjsip_hdr *hdr=(const pjsip_hdr*) start, *end=hdr_list;
+ 
+     if (hdr == NULL) {
+-	hdr = msg->hdr.next;
++	hdr = end->next;
+     }
+     for (; hdr!=end; hdr = hdr->next) {
+ 	if (pj_stricmp(&hdr->name, name) == 0)
+@@ -365,15 +365,15 @@ PJ_DEF(void*)  pjsip_msg_find_hdr_by_nam
+     return NULL;
+ }
+ 
+-PJ_DEF(void*)  pjsip_msg_find_hdr_by_names( const pjsip_msg *msg, 
+-					    const pj_str_t *name, 
+-					    const pj_str_t *sname,
+-					    const void *start)
++PJ_DEF(void*)  pjsip_hdr_find_by_names( const void *hdr_list,
++					const pj_str_t *name,
++					const pj_str_t *sname,
++					const void *start)
+ {
+-    const pjsip_hdr *hdr=(const pjsip_hdr*)start, *end=&msg->hdr;
++    const pjsip_hdr *hdr=(const pjsip_hdr*) start, *end=hdr_list;
+ 
+     if (hdr == NULL) {
+-	hdr = msg->hdr.next;
++	hdr = end->next;
+     }
+     for (; hdr!=end; hdr = hdr->next) {
+ 	if (pj_stricmp(&hdr->name, name) == 0)
+@@ -384,6 +384,27 @@ PJ_DEF(void*)  pjsip_msg_find_hdr_by_nam
+     return NULL;
+ }
+ 
++PJ_DEF(void*)  pjsip_msg_find_hdr( const pjsip_msg *msg,
++				   pjsip_hdr_e hdr_type, const void *start)
++{
++    return pjsip_hdr_find(&msg->hdr, hdr_type, start);
++}
++
++PJ_DEF(void*)  pjsip_msg_find_hdr_by_name( const pjsip_msg *msg,
++					   const pj_str_t *name,
++					   const void *start)
++{
++    return pjsip_hdr_find_by_name(&msg->hdr, name, start);
++}
++
++PJ_DEF(void*)  pjsip_msg_find_hdr_by_names( const pjsip_msg *msg,
++					    const pj_str_t *name,
++					    const pj_str_t *sname,
++					    const void *start)
++{
++    return pjsip_hdr_find_by_names(&msg->hdr, name, sname, start);
++}
++
+ PJ_DEF(void*) pjsip_msg_find_remove_hdr( pjsip_msg *msg, 
+ 				         pjsip_hdr_e hdr_type, void *start)
+ {
diff --git a/external/management/libs/pjproject/patches/0160-Additional-multipart-improvements.patch b/external/management/libs/pjproject/patches/0160-Additional-multipart-improvements.patch
new file mode 100644
index 0000000..3de67b5
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0160-Additional-multipart-improvements.patch
@@ -0,0 +1,635 @@
+From b7ecff22e77887626fd8e8608c4dd73bc7b7366f Mon Sep 17 00:00:00 2001
+From: George Joseph <gjoseph@sangoma.com>
+Date: Tue, 18 Jan 2022 06:14:31 -0700
+Subject: [PATCH] Additional multipart improvements
+
+Added the following APIs:
+pjsip_multipart_find_part_by_header()
+pjsip_multipart_find_part_by_header_str()
+pjsip_multipart_find_part_by_cid_str()
+pjsip_multipart_find_part_by_cid_uri()
+---
+ pjsip/include/pjsip/sip_multipart.h |  83 ++++++++++
+ pjsip/src/pjsip/sip_multipart.c     | 223 +++++++++++++++++++++++++++
+ pjsip/src/test/multipart_test.c     | 225 +++++++++++++++++++++++++++-
+ 3 files changed, 530 insertions(+), 1 deletion(-)
+
+--- a/pjsip/include/pjsip/sip_multipart.h
++++ b/pjsip/include/pjsip/sip_multipart.h
+@@ -154,6 +154,89 @@ pjsip_multipart_find_part( const pjsip_m
+ 			   const pjsip_multipart_part *start);
+ 
+ /**
++ * Find a body inside multipart bodies which has a header matching the
++ * supplied one. Most useful for finding a part with a specific Content-ID.
++ *
++ * @param pool		Memory pool to use for temp space.
++ * @param mp		The multipart body.
++ * @param search_hdr	Header to search for.
++ * @param start		If specified, the search will begin at
++ * 			start->next part. Otherwise it will begin at
++ * 			the first part in the multipart bodies.
++ *
++ * @return		The first part which has a header matching the
++ * 			specified one, or NULL if not found.
++ */
++PJ_DECL(pjsip_multipart_part*)
++pjsip_multipart_find_part_by_header(pj_pool_t *pool,
++				    const pjsip_msg_body *mp,
++				    void *search_hdr,
++				    const pjsip_multipart_part *start);
++
++/**
++ * Find a body inside multipart bodies which has a header matching the
++ * supplied name and value. Most useful for finding a part with a specific
++ * Content-ID.
++ *
++ * @param pool		Memory pool to use for temp space.
++ * @param mp		The multipart body.
++ * @param hdr_name	Header name to search for.
++ * @param hdr_value	Header value search for.
++ * @param start		If specified, the search will begin at
++ * 			start->next part. Otherwise it will begin at
++ * 			the first part in the multipart bodies.
++ *
++ * @return		The first part which has a header matching the
++ * 			specified one, or NULL if not found.
++ */
++PJ_DECL(pjsip_multipart_part*)
++pjsip_multipart_find_part_by_header_str(pj_pool_t *pool,
++				    const pjsip_msg_body *mp,
++				    const pj_str_t *hdr_name,
++				    const pj_str_t *hdr_value,
++				    const pjsip_multipart_part *start);
++
++
++
++/**
++ * Find a body inside multipart bodies which has a Content-ID value matching the
++ * supplied "cid" URI in pj_str form.  The "cid:" scheme will be assumed if the
++ * URL doesn't start with it.  Enclosing angle brackets will also be handled
++ * correctly if they exist.
++ *
++ * @see RFC2392 Content-ID and Message-ID Uniform Resource Locators
++ *
++ * @param pool	Memory pool to use for temp space.
++ * @param mp	The multipart body.
++ * @param cid	The "cid" URI to search for in pj_str form.
++ *
++ * @return		The first part which has a Content-ID header matching the
++ * 			specified "cid" URI. or NULL if not found.
++ */
++PJ_DECL(pjsip_multipart_part*)
++pjsip_multipart_find_part_by_cid_str(pj_pool_t *pool,
++				 const pjsip_msg_body *mp,
++				 pj_str_t *cid);
++
++/**
++ * Find a body inside multipart bodies which has a Content-ID value matching the
++ * supplied "cid" URI.
++ *
++ * @see RFC2392 Content-ID and Message-ID Uniform Resource Locators
++ *
++ * @param pool	Memory pool to use for temp space.
++ * @param mp	The multipart body.
++ * @param cid	The "cid" URI to search for.
++ *
++ * @return		The first part which had a Content-ID header matching the
++ * 			specified "cid" URI. or NULL if not found.
++ */
++PJ_DECL(pjsip_multipart_part*)
++pjsip_multipart_find_part_by_cid_uri(pj_pool_t *pool,
++				 const pjsip_msg_body *mp,
++				 pjsip_other_uri *cid_uri);
++
++/**
+  * Parse multipart message.
+  *
+  * @param pool		Memory pool.
+--- a/pjsip/src/pjsip/sip_multipart.c
++++ b/pjsip/src/pjsip/sip_multipart.c
+@@ -19,6 +19,7 @@
+ #include <pjsip/sip_multipart.h>
+ #include <pjsip/sip_parser.h>
+ #include <pjlib-util/scanner.h>
++#include <pjlib-util/string.h>
+ #include <pj/assert.h>
+ #include <pj/ctype.h>
+ #include <pj/errno.h>
+@@ -416,6 +417,220 @@ pjsip_multipart_find_part( const pjsip_m
+     return NULL;
+ }
+ 
++/*
++ * Find a body inside multipart bodies which has the header and value.
++ */
++PJ_DEF(pjsip_multipart_part*)
++pjsip_multipart_find_part_by_header_str(pj_pool_t *pool,
++				    const pjsip_msg_body *mp,
++				    const pj_str_t *hdr_name,
++				    const pj_str_t *hdr_value,
++				    const pjsip_multipart_part *start)
++{
++    struct multipart_data *m_data;
++    pjsip_multipart_part *part;
++    pjsip_hdr *found_hdr;
++    pj_str_t found_hdr_str;
++    pj_str_t found_hdr_value;
++    pj_size_t expected_hdr_slen;
++    pj_size_t buf_size;
++    int hdr_name_len;
++#define REASONABLE_PADDING 32
++#define SEPARATOR_LEN 2
++    /* Must specify mandatory params */
++    PJ_ASSERT_RETURN(mp && hdr_name && hdr_value, NULL);
++
++    /* mp must really point to an actual multipart msg body */
++    PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
++
++    /*
++     * We'll need to "print" each header we find to test it but
++     * allocating a buffer of PJSIP_MAX_URL_SIZE is overkill.
++     * Instead, we'll allocate one large enough to hold the search
++     * header name, the ": " separator, the search hdr value, and
++     * the NULL terminator.  If we can't print the found header
++     * into that buffer then it can't be a match.
++     *
++     * Some header print functions such as generic_int require enough
++     * space to print the maximum possible header length so we'll
++     * add a reasonable amount to the print buffer size.
++     */
++    expected_hdr_slen = hdr_name->slen + SEPARATOR_LEN + hdr_value->slen;
++    buf_size = expected_hdr_slen + REASONABLE_PADDING;
++    found_hdr_str.ptr = pj_pool_alloc(pool, buf_size);
++    found_hdr_str.slen = 0;
++    hdr_name_len = hdr_name->slen + SEPARATOR_LEN;
++
++    m_data = (struct multipart_data*)mp->data;
++
++    if (start)
++	part = start->next;
++    else
++	part = m_data->part_head.next;
++
++    while (part != &m_data->part_head) {
++	found_hdr = NULL;
++	while ((found_hdr = pjsip_hdr_find_by_name(&part->hdr, hdr_name,
++	    (found_hdr ? found_hdr->next : NULL))) != NULL) {
++
++	    found_hdr_str.slen = pjsip_hdr_print_on((void*) found_hdr, found_hdr_str.ptr, buf_size);
++	    /*
++	     * If the buffer was too small (slen = -1) or the result wasn't
++	     * the same length as the search header, it can't be a match.
++	     */
++	    if (found_hdr_str.slen != expected_hdr_slen) {
++		continue;
++	    }
++	    /*
++	     * Set the value overlay to start at the found header value...
++	     */
++	    found_hdr_value.ptr = found_hdr_str.ptr + hdr_name_len;
++	    found_hdr_value.slen = found_hdr_str.slen - hdr_name_len;
++	    /* ...and compare it to the supplied header value. */
++	    if (pj_strcmp(hdr_value, &found_hdr_value) == 0) {
++		return part;
++	    }
++	}
++	part = part->next;
++    }
++    return NULL;
++#undef SEPARATOR_LEN
++#undef REASONABLE_PADDING
++}
++
++PJ_DEF(pjsip_multipart_part*)
++pjsip_multipart_find_part_by_header(pj_pool_t *pool,
++				    const pjsip_msg_body *mp,
++				    void *search_for,
++				    const pjsip_multipart_part *start)
++{
++    struct multipart_data *m_data;
++    pjsip_hdr *search_hdr = search_for;
++    pj_str_t search_buf;
++
++    /* Must specify mandatory params */
++    PJ_ASSERT_RETURN(mp && search_hdr, NULL);
++
++    /* mp must really point to an actual multipart msg body */
++    PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
++
++    /*
++     * Unfortunately, there isn't enough information to determine
++     * the maximum printed size of search_hdr at this point so we
++     * have to allocate a reasonable max.
++     */
++    search_buf.ptr = pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
++    search_buf.slen = pjsip_hdr_print_on(search_hdr, search_buf.ptr, PJSIP_MAX_URL_SIZE - 1);
++    if (search_buf.slen <= 0) {
++	return NULL;
++    }
++    /*
++     * Set the header value to start after the header name plus the ":", then
++     * strip leading and trailing whitespace.
++     */
++    search_buf.ptr += (search_hdr->name.slen + 1);
++    search_buf.slen -= (search_hdr->name.slen + 1);
++    pj_strtrim(&search_buf);
++
++    return pjsip_multipart_find_part_by_header_str(pool, mp, &search_hdr->name, &search_buf, start);
++}
++
++/*
++ * Convert a Content-ID URI to it's corresponding header value.
++ * RFC2392 says...
++ * A "cid" URL is converted to the corresponding Content-ID message
++ * header by removing the "cid:" prefix, converting the % encoded
++ * character(s) to their equivalent US-ASCII characters, and enclosing
++ * the remaining parts with an angle bracket pair, "<" and ">".
++ *
++ * This implementation will accept URIs with or without the "cid:"
++ * scheme and optional angle brackets.
++ */
++static pj_str_t cid_uri_to_hdr_value(pj_pool_t *pool, pj_str_t *cid_uri)
++{
++    pj_size_t cid_len = pj_strlen(cid_uri);
++    pj_size_t alloc_len = cid_len + 2 /* for the leading and trailing angle brackets */;
++    pj_str_t uri_overlay;
++    pj_str_t cid_hdr;
++    pj_str_t hdr_overlay;
++
++    pj_strassign(&uri_overlay, cid_uri);
++    /* If the URI is already enclosed in angle brackets, remove them. */
++    if (uri_overlay.ptr[0] == '<') {
++	uri_overlay.ptr++;
++	uri_overlay.slen -= 2;
++    }
++    /* If the URI starts with the "cid:" scheme, skip over it. */
++    if (pj_strncmp2(&uri_overlay, "cid:", 4) == 0) {
++	uri_overlay.ptr += 4;
++	uri_overlay.slen -= 4;
++    }
++    /* Start building */
++    cid_hdr.ptr = pj_pool_alloc(pool, alloc_len);
++    cid_hdr.ptr[0] = '<';
++    cid_hdr.slen = 1;
++    hdr_overlay.ptr = cid_hdr.ptr + 1;
++    hdr_overlay.slen = 0;
++    pj_strcpy_unescape(&hdr_overlay, &uri_overlay);
++    cid_hdr.slen += hdr_overlay.slen;
++    cid_hdr.ptr[cid_hdr.slen] = '>';
++    cid_hdr.slen++;
++
++    return cid_hdr;
++}
++
++PJ_DEF(pjsip_multipart_part*)
++pjsip_multipart_find_part_by_cid_str(pj_pool_t *pool,
++				 const pjsip_msg_body *mp,
++				 pj_str_t *cid)
++{
++    struct multipart_data *m_data;
++    pjsip_multipart_part *part;
++    pjsip_generic_string_hdr *found_hdr;
++    pj_str_t found_hdr_value;
++    static pj_str_t hdr_name = { "Content-ID", 10};
++    pj_str_t hdr_value;
++
++    PJ_ASSERT_RETURN(pool && mp && cid && (pj_strlen(cid) > 0), NULL);
++
++    hdr_value = cid_uri_to_hdr_value(pool, cid);
++    if (pj_strlen(&hdr_value) == 0) {
++	return NULL;
++    }
++
++    m_data = (struct multipart_data*)mp->data;
++    part = m_data->part_head.next;
++
++    while (part != &m_data->part_head) {
++	found_hdr = NULL;
++	while ((found_hdr = pjsip_hdr_find_by_name(&part->hdr, &hdr_name,
++	    (found_hdr ? found_hdr->next : NULL))) != NULL) {
++	    if (pj_strcmp(&hdr_value, &found_hdr->hvalue) == 0) {
++		return part;
++	    }
++	}
++	part = part->next;
++    }
++    return NULL;
++}
++
++PJ_DEF(pjsip_multipart_part*)
++pjsip_multipart_find_part_by_cid_uri(pj_pool_t *pool,
++				 const pjsip_msg_body *mp,
++				 pjsip_other_uri *cid_uri)
++{
++    PJ_ASSERT_RETURN(pool && mp && cid_uri, NULL);
++
++    if (pj_strcmp2(&cid_uri->scheme, "cid") != 0) {
++	return NULL;
++    }
++    /*
++     * We only need to pass the URI content so we
++     * can do that directly.
++     */
++    return pjsip_multipart_find_part_by_cid_str(pool, mp, &cid_uri->content);
++}
++
+ /* Parse a multipart part. "pct" is parent content-type  */
+ static pjsip_multipart_part *parse_multipart_part(pj_pool_t *pool,
+ 						  char *start,
+@@ -584,6 +799,7 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_
+ 		(int)boundary.slen, boundary.ptr));
+     }
+ 
++
+     /* Build the delimiter:
+      *   delimiter = "--" boundary
+      */
+@@ -630,6 +846,8 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_
+ 	if (*curptr=='\r') ++curptr;
+ 	if (*curptr!='\n') {
+ 	    /* Expecting a newline here */
++	    PJ_LOG(2, (THIS_FILE, "Failed to find newline"));
++
+ 	    return NULL;
+ 	}
+ 	++curptr;
+@@ -645,6 +863,7 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_
+ 	    curptr = pj_strstr(&subbody, &delim);
+ 	    if (!curptr) {
+ 		/* We're really expecting end delimiter to be found. */
++		PJ_LOG(2, (THIS_FILE, "Failed to find end-delimiter"));
+ 		return NULL;
+ 	    }
+ 	}
+@@ -670,9 +889,13 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_
+ 	part = parse_multipart_part(pool, start_body, end_body - start_body,
+ 				    ctype);
+ 	if (part) {
++	    TRACE_((THIS_FILE, "Adding part"));
+ 	    pjsip_multipart_add_part(pool, body, part);
++	} else {
++	    PJ_LOG(2, (THIS_FILE, "Failed to add part"));
+ 	}
+     }
++    TRACE_((THIS_FILE, "pjsip_multipart_parse finished: %p", body));
+ 
+     return body;
+ }
+--- a/pjsip/src/test/multipart_test.c
++++ b/pjsip/src/test/multipart_test.c
+@@ -28,6 +28,7 @@
+ typedef pj_status_t (*verify_ptr)(pj_pool_t*,pjsip_msg_body*);
+ 
+ static pj_status_t verify1(pj_pool_t *pool, pjsip_msg_body *body);
++static pj_status_t verify2(pj_pool_t *pool, pjsip_msg_body *body);
+ 
+ static struct test_t
+ {
+@@ -68,7 +69,41 @@ static struct test_t
+ 		"This is epilogue, which should be ignored too",
+ 
+ 		&verify1
++	},
++	{
++		/* Content-type */
++		"multipart", "mixed", "12345",
++
++		/* Body: */
++		"This is the prolog, which should be ignored.\r\n"
++		"--12345\r\n"
++		"Content-Type: text/plain\r\n"
++		"Content-ID: <header1@example.org>\r\n"
++		"Content-ID: <\"header1\"@example.org>\r\n"
++		"Content-Length: 13\r\n"
++		"\r\n"
++		"has header1\r\n"
++		"--12345 \t\r\n"
++		"Content-Type: application/pidf+xml\r\n"
++		"Content-ID: <my header2@example.org>\r\n"
++		"Content-ID: <my\xffheader2@example.org>\r\n"
++		"Content-Length: 13\r\n"
++		"\r\n"
++		"has header2\r\n"
++		"--12345\r\n"
++		"Content-Type: text/plain\r\n"
++		"Content-ID: <my header3@example.org>\r\n"
++		"Content-ID: <header1@example.org>\r\n"
++		"Content-ID: <my header4@example.org>\r\n"
++		"Content-Length: 13\r\n"
++		"\r\n"
++		"has header4\r\n"
++		"--12345--\r\n"
++		"This is epilogue, which should be ignored too",
++
++		&verify2
+ 	}
++
+ };
+ 
+ static void init_media_type(pjsip_media_type *mt,
+@@ -87,6 +122,192 @@ static void init_media_type(pjsip_media_
+     }
+ }
+ 
++static int verify_hdr(pj_pool_t *pool, pjsip_msg_body *multipart_body,
++    void *hdr, char *part_body)
++{
++    pjsip_media_type mt;
++    pjsip_multipart_part *part;
++    pj_str_t the_body;
++
++
++    part = pjsip_multipart_find_part_by_header(pool, multipart_body, hdr, NULL);
++    if (!part) {
++	return -1;
++    }
++
++    the_body.ptr = (char*)part->body->data;
++    the_body.slen = part->body->len;
++
++    if (pj_strcmp2(&the_body, part_body) != 0) {
++	return -2;
++    }
++
++    return 0;
++}
++
++static int verify_cid_str(pj_pool_t *pool, pjsip_msg_body *multipart_body,
++    pj_str_t cid_url, char *part_body)
++{
++    pjsip_media_type mt;
++    pjsip_multipart_part *part;
++    pj_str_t the_body;
++
++    part = pjsip_multipart_find_part_by_cid_str(pool, multipart_body, &cid_url);
++    if (!part) {
++	return -3;
++    }
++
++    the_body.ptr = (char*)part->body->data;
++    the_body.slen = part->body->len;
++
++    if (pj_strcmp2(&the_body, part_body) != 0) {
++	return -4;
++    }
++
++    return 0;
++}
++
++static int verify_cid_uri(pj_pool_t *pool, pjsip_msg_body *multipart_body,
++    pjsip_other_uri *cid_uri, char *part_body)
++{
++    pjsip_media_type mt;
++    pjsip_multipart_part *part;
++    pj_str_t the_body;
++
++    part = pjsip_multipart_find_part_by_cid_uri(pool, multipart_body, cid_uri);
++    if (!part) {
++	return -5;
++    }
++
++    the_body.ptr = (char*)part->body->data;
++    the_body.slen = part->body->len;
++
++    if (pj_strcmp2(&the_body, part_body) != 0) {
++	return -6;
++    }
++
++    return 0;
++}
++
++static pj_status_t verify2(pj_pool_t *pool, pjsip_msg_body *body)
++{
++    int rc = 0;
++    int rcbase = 300;
++    pjsip_other_uri *cid_uri;
++    pjsip_ctype_hdr *ctype_hdr = pjsip_ctype_hdr_create(pool);
++
++    ctype_hdr->media.type = pj_str("application");
++    ctype_hdr->media.subtype = pj_str("pidf+xml");
++
++    rc = verify_hdr(pool, body, ctype_hdr, "has header2");
++    if (rc) {
++	return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("cid:header1@example.org"), "has header1");
++    if (rc) {
++	return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("%22header1%22@example.org"), "has header1");
++    if (rc) {
++	return (rc - rcbase);
++    }
++
++    cid_uri = pjsip_uri_get_uri(pjsip_parse_uri(pool, "<cid:%22header1%22@example.org>",
++	strlen("<cid:%22header1%22@example.org>"), 0));
++    rcbase += 10;
++    rc = verify_cid_uri(pool, body, cid_uri, "has header1");
++    if (rc) {
++	return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("<cid:my%20header2@example.org>"), "has header2");
++    if (rc) {
++	return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("cid:my%ffheader2@example.org"), "has header2");
++    if (rc) {
++	return (rc - rcbase);
++    }
++
++    cid_uri = pjsip_uri_get_uri(pjsip_parse_uri(pool, "<cid:my%ffheader2@example.org>",
++	strlen("<cid:my%ffheader2@example.org>"), 0));
++    rcbase += 10;
++    rc = verify_cid_uri(pool, body, cid_uri, "has header2");
++    if (rc) {
++	return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("cid:my%20header3@example.org"), "has header4");
++    if (rc) {
++	return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("<cid:my%20header4@example.org>"), "has header4");
++    if (rc) {
++	return (rc - rcbase);
++    }
++
++    cid_uri = pjsip_uri_get_uri(pjsip_parse_uri(pool, "<cid:my%20header4@example.org>",
++	strlen("<cid:my%20header4@example.org>"), 0));
++    rcbase += 10;
++    rc = verify_cid_uri(pool, body, cid_uri, "has header4");
++    if (rc) {
++	return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("<my%20header3@example.org>"), "has header4");
++    if (rc) {
++	return (rc - rcbase);
++    }
++
++    /* These should all fail for malformed or missing URI */
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("cid:"), "has header4");
++    if (!rc) {
++	return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str(""), "has header4");
++    if (!rc) {
++	return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("<>"), "has header4");
++    if (!rc) {
++	return (rc - rcbase);
++    }
++
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("<cid>"), "has header4");
++    if (!rc) {
++	return (rc - rcbase);
++    }
++
++    /*
++     * This is going to pass but the ' ' in the uri is un-encoded which is invalid
++     * so we should never see it.
++     */
++    rcbase += 10;
++    rc = verify_cid_str(pool, body, pj_str("cid:my header3@example.org"), "has header4");
++    if (rc) {
++	return (rc - rcbase);
++    }
++
++    return 0;
++}
++
+ static int verify_part(pjsip_multipart_part *part,
+ 		       char *h_content_type,
+ 		       char *h_content_subtype,
+@@ -236,8 +457,10 @@ static int parse_test(void)
+ 
+ 	pj_strdup2_with_null(pool, &str, p_tests[i].msg);
+ 	body = pjsip_multipart_parse(pool, str.ptr, str.slen, &ctype, 0);
+-	if (!body)
++	if (!body) {
++	    pj_pool_release(pool);
+ 	    return -100;
++	}
+ 
+ 	if (p_tests[i].verify) {
+ 	    rc = p_tests[i].verify(pool, body);
diff --git a/external/management/libs/pjproject/patches/0170-stun-integer-underflow.patch b/external/management/libs/pjproject/patches/0170-stun-integer-underflow.patch
new file mode 100644
index 0000000..519dc81
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0170-stun-integer-underflow.patch
@@ -0,0 +1,21 @@
+From 15663e3f37091069b8c98a7fce680dc04bc8e865 Mon Sep 17 00:00:00 2001
+From: sauwming <ming@teluu.com>
+Date: Tue, 10 Aug 2021 11:53:25 +0800
+Subject: [PATCH] Merge pull request from GHSA-2qpg-f6wf-w984
+
+---
+ pjnath/src/pjnath/stun_msg.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/pjnath/src/pjnath/stun_msg.c
++++ b/pjnath/src/pjnath/stun_msg.c
+@@ -1763,6 +1763,9 @@ static pj_status_t decode_errcode_attr(p
+     /* Get pointer to the string in the message */
+     value.ptr = ((char*)buf + ATTR_HDR_LEN + 4);
+     value.slen = attr->hdr.length - 4;
++    /* Make sure the length is never negative */
++    if (value.slen < 0)
++    	value.slen = 0;
+ 
+     /* Copy the string to the attribute */
+     pj_strdup(pool, &attr->reason, &value);
diff --git a/external/management/libs/pjproject/patches/0171-dialog-set-free.patch b/external/management/libs/pjproject/patches/0171-dialog-set-free.patch
new file mode 100644
index 0000000..d113117
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0171-dialog-set-free.patch
@@ -0,0 +1,109 @@
+From db3235953baa56d2fb0e276ca510fefca751643f Mon Sep 17 00:00:00 2001
+From: Nanang Izzuddin <nanang@teluu.com>
+Date: Mon, 21 Feb 2022 06:24:52 +0700
+Subject: [PATCH] Merge pull request from GHSA-ffff-m5fm-qm62
+
+* Update pjsip_ua_unregister_dlg():
+- update the hash key if the dialog being unregistered is used as hash key.
+- add an assertion check to make sure that the dlg_set to be removed is valid (can be found in the hash table).
+
+* Change hash key string comparison method.
+---
+ pjsip/src/pjsip/sip_ua_layer.c | 48 +++++++++++++++++++++++++++++-----
+ 1 file changed, 42 insertions(+), 6 deletions(-)
+
+--- a/pjsip/src/pjsip/sip_ua_layer.c
++++ b/pjsip/src/pjsip/sip_ua_layer.c
+@@ -65,6 +65,9 @@ struct dlg_set
+     /* This is the buffer to store this entry in the hash table. */
+     pj_hash_entry_buf ht_entry;
+ 
++    /* Entry key in the hash table */
++    pj_str_t ht_key;
++
+     /* List of dialog in this dialog set. */
+     struct dlg_set_head  dlg_list;
+ };
+@@ -321,6 +324,7 @@ PJ_DEF(pj_status_t) pjsip_ua_register_dl
+ 	     * Create the dialog set and add this dialog to it.
+ 	     */
+ 	    dlg_set = alloc_dlgset_node();
++	    dlg_set->ht_key = dlg->local.info->tag;
+ 	    pj_list_init(&dlg_set->dlg_list);
+ 	    pj_list_push_back(&dlg_set->dlg_list, dlg);
+ 
+@@ -328,8 +332,8 @@ PJ_DEF(pj_status_t) pjsip_ua_register_dl
+ 
+ 	    /* Register the dialog set in the hash table. */
+ 	    pj_hash_set_np_lower(mod_ua.dlg_table, 
+-			         dlg->local.info->tag.ptr,
+-                                 (unsigned)dlg->local.info->tag.slen,
++			         dlg_set->ht_key.ptr,
++                                 (unsigned)dlg_set->ht_key.slen,
+ 			         dlg->local.tag_hval, dlg_set->ht_entry,
+                                  dlg_set);
+ 	}
+@@ -339,14 +343,15 @@ PJ_DEF(pj_status_t) pjsip_ua_register_dl
+ 	struct dlg_set *dlg_set;
+ 
+ 	dlg_set = alloc_dlgset_node();
++	dlg_set->ht_key = dlg->local.info->tag;
+ 	pj_list_init(&dlg_set->dlg_list);
+ 	pj_list_push_back(&dlg_set->dlg_list, dlg);
+ 
+ 	dlg->dlg_set = dlg_set;
+ 
+ 	pj_hash_set_np_lower(mod_ua.dlg_table, 
+-		             dlg->local.info->tag.ptr,
+-                             (unsigned)dlg->local.info->tag.slen,
++		             dlg_set->ht_key.ptr,
++                             (unsigned)dlg_set->ht_key.slen,
+ 		             dlg->local.tag_hval, dlg_set->ht_entry, dlg_set);
+     }
+ 
+@@ -391,12 +396,43 @@ PJ_DEF(pj_status_t) pjsip_ua_unregister_
+ 
+     /* If dialog list is empty, remove the dialog set from the hash table. */
+     if (pj_list_empty(&dlg_set->dlg_list)) {
+-	pj_hash_set_lower(NULL, mod_ua.dlg_table, dlg->local.info->tag.ptr,
+-		          (unsigned)dlg->local.info->tag.slen, 
++
++	/* Verify that the dialog set is valid */
++	pj_assert(pj_hash_get_lower(mod_ua.dlg_table, dlg_set->ht_key.ptr,
++				    (unsigned)dlg_set->ht_key.slen,
++				    &dlg->local.tag_hval) == dlg_set);
++
++	pj_hash_set_lower(NULL, mod_ua.dlg_table, dlg_set->ht_key.ptr,
++		          (unsigned)dlg_set->ht_key.slen,
+ 			  dlg->local.tag_hval, NULL);
+ 
+ 	/* Return dlg_set to free nodes. */
+ 	pj_list_push_back(&mod_ua.free_dlgset_nodes, dlg_set);
++    } else {
++	/* If the just unregistered dialog is being used as hash key,
++	 * reset the dlg_set entry with a new key (i.e: from the first dialog
++	 * in dlg_set).
++	 */
++	if (dlg_set->ht_key.ptr  == dlg->local.info->tag.ptr &&
++	    dlg_set->ht_key.slen == dlg->local.info->tag.slen)
++	{
++	    pjsip_dialog* key_dlg = dlg_set->dlg_list.next;
++
++	    /* Verify that the old & new keys share the hash value */
++	    pj_assert(key_dlg->local.tag_hval == dlg->local.tag_hval);
++
++	    pj_hash_set_lower(NULL, mod_ua.dlg_table, dlg_set->ht_key.ptr,
++			      (unsigned)dlg_set->ht_key.slen,
++			      dlg->local.tag_hval, NULL);
++
++	    dlg_set->ht_key = key_dlg->local.info->tag;
++
++	    pj_hash_set_np_lower(mod_ua.dlg_table,
++				 dlg_set->ht_key.ptr,
++				 (unsigned)dlg_set->ht_key.slen,
++				 key_dlg->local.tag_hval, dlg_set->ht_entry,
++				 dlg_set);
++	}
+     }
+ 
+     /* Unlock user agent. */
diff --git a/external/management/libs/pjproject/patches/0172-prevent-multipart-oob.patch b/external/management/libs/pjproject/patches/0172-prevent-multipart-oob.patch
new file mode 100644
index 0000000..d3de936
--- /dev/null
+++ b/external/management/libs/pjproject/patches/0172-prevent-multipart-oob.patch
@@ -0,0 +1,37 @@
+From 077b465c33f0aec05a49cd2ca456f9a1b112e896 Mon Sep 17 00:00:00 2001
+From: sauwming <ming@teluu.com>
+Date: Wed, 26 Jan 2022 13:28:57 +0800
+Subject: [PATCH] Merge pull request from GHSA-7fw8-54cv-r7pm
+
+---
+ pjlib-util/src/pjlib-util/scanner.c | 13 +++++++++----
+ 1 file changed, 9 insertions(+), 4 deletions(-)
+
+--- a/pjlib-util/src/pjlib-util/scanner.c
++++ b/pjlib-util/src/pjlib-util/scanner.c
+@@ -444,16 +444,21 @@ PJ_DEF(void) pj_scan_get_n( pj_scanner *
+ 
+ PJ_DEF(int) pj_scan_get_char( pj_scanner *scanner )
+ {
+-    int chr = *scanner->curptr;
++    register char *s = scanner->curptr;
++    int chr;
+ 
+-    if (!chr) {
++    if (s >= scanner->end || !*s) {
+ 	pj_scan_syntax_err(scanner);
+ 	return 0;
+     }
+ 
+-    ++scanner->curptr;
++    chr = *s;
+ 
+-    if (PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws) {
++    ++s;
++    scanner->curptr = s;
++    if (PJ_SCAN_CHECK_EOF(s) && PJ_SCAN_IS_PROBABLY_SPACE(*s) &&
++    	scanner->skip_ws)
++    {
+ 	pj_scan_skip_whitespace(scanner);
+     }
+     return chr;