Commit 4ec7509d93 for asterisk.org

commit 4ec7509d939e187cc3644e2ef9a6447b9770d4fe
Author: Stanislav Abramenkov <stas.abramenkov@gmail.com>
Date:   Mon Apr 27 20:05:07 2026 +0300

    Upgrade bundled pjproject to 2.17.

    Resolves: #1888

    UserNote: Bundled pjproject has been upgraded to 2.17. For more
    information about what is included in this release, see the
    pjproject Github page: https://github.com/pjsip/pjproject/releases/tag/2.17

diff --git a/third-party/pjproject/patches/0000-remove-third-party.patch b/third-party/pjproject/patches/0000-remove-third-party.patch
index ab6c6d1ec6..77a6089d99 100644
--- a/third-party/pjproject/patches/0000-remove-third-party.patch
+++ b/third-party/pjproject/patches/0000-remove-third-party.patch
@@ -21,7 +21,7 @@ index 4bc464f8c..80681d961 100644
 -APP_THIRD_PARTY_LIBS += -lsrtp-$(TARGET_NAME)
 -else
 -APP_THIRD_PARTY_LIBS += -lsrtp
--APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libsrtp.$(SHLIB_SUFFIX).$(PJ_VERSION_MAJOR) $(PJ_DIR)/third_party/lib/libsrtp.$(SHLIB_SUFFIX)
+-APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libsrtp.$(SHLIB_SUFFIX).$(PJ_SONAME_VERSION) $(PJ_DIR)/third_party/lib/libsrtp.$(SHLIB_SUFFIX)
 -endif
 -endif
 -endif
@@ -43,7 +43,7 @@ index 4bc464f8c..80681d961 100644
 -APP_THIRD_PARTY_LIBS += -lgsmcodec-$(TARGET_NAME)
 -else
 -APP_THIRD_PARTY_LIBS += -lgsmcodec
--APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libgsmcodec.$(SHLIB_SUFFIX).$(PJ_VERSION_MAJOR) $(PJ_DIR)/third_party/lib/libgsmcodec.$(SHLIB_SUFFIX)
+-APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libgsmcodec.$(SHLIB_SUFFIX).$(PJ_SONAME_VERSION) $(PJ_DIR)/third_party/lib/libgsmcodec.$(SHLIB_SUFFIX)
 -endif
 -endif
 -endif
@@ -57,7 +57,7 @@ index 4bc464f8c..80681d961 100644
 -APP_THIRD_PARTY_LIBS += -lspeex-$(TARGET_NAME)
 -else
 -APP_THIRD_PARTY_LIBS += -lspeex
--APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libspeex.$(SHLIB_SUFFIX).$(PJ_VERSION_MAJOR) $(PJ_DIR)/third_party/lib/libspeex.$(SHLIB_SUFFIX)
+-APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libspeex.$(SHLIB_SUFFIX).$(PJ_SONAME_VERSION) $(PJ_DIR)/third_party/lib/libspeex.$(SHLIB_SUFFIX)
 -endif
 -endif
 -endif
@@ -68,7 +68,7 @@ index 4bc464f8c..80681d961 100644
 -APP_THIRD_PARTY_LIBS += -lilbccodec-$(TARGET_NAME)
 -else
 -APP_THIRD_PARTY_LIBS += -lilbccodec
--APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libilbccodec.$(SHLIB_SUFFIX).$(PJ_VERSION_MAJOR) $(PJ_DIR)/third_party/lib/libilbccodec.$(SHLIB_SUFFIX)
+-APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libilbccodec.$(SHLIB_SUFFIX).$(PJ_SONAME_VERSION) $(PJ_DIR)/third_party/lib/libilbccodec.$(SHLIB_SUFFIX)
 -endif
 -endif
 -
@@ -78,7 +78,7 @@ index 4bc464f8c..80681d961 100644
 -APP_THIRD_PARTY_LIBS += -lg7221codec-$(TARGET_NAME)
 -else
 -APP_THIRD_PARTY_LIBS += -lg7221codec
--APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libg7221codec.$(SHLIB_SUFFIX).$(PJ_VERSION_MAJOR) $(PJ_DIR)/third_party/lib/libg7221codec.$(SHLIB_SUFFIX)
+-APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libg7221codec.$(SHLIB_SUFFIX).$(PJ_SONAME_VERSION) $(PJ_DIR)/third_party/lib/libg7221codec.$(SHLIB_SUFFIX)
 -endif
 -endif
 -
@@ -96,7 +96,7 @@ index 4bc464f8c..80681d961 100644
 -APP_THIRD_PARTY_LIBS += -lyuv-$(TARGET_NAME)
 -else
 -APP_THIRD_PARTY_LIBS += -lyuv
--APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libyuv.$(SHLIB_SUFFIX).$(PJ_VERSION_MAJOR) $(PJ_DIR)/third_party/lib/libyuv.$(SHLIB_SUFFIX)
+-APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libyuv.$(SHLIB_SUFFIX).$(PJ_SONAME_VERSION) $(PJ_DIR)/third_party/lib/libyuv.$(SHLIB_SUFFIX)
 -endif
 -endif
 -endif
@@ -110,7 +110,7 @@ index 4bc464f8c..80681d961 100644
 -APP_THIRD_PARTY_LIBS += -lwebrtc-$(TARGET_NAME)
 -else
 -APP_THIRD_PARTY_LIBS += -lwebrtc
--APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libwebrtc.$(SHLIB_SUFFIX).$(PJ_VERSION_MAJOR) $(PJ_DIR)/third_party/lib/libwebrtc.$(SHLIB_SUFFIX)
+-APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libwebrtc.$(SHLIB_SUFFIX).$(PJ_SONAME_VERSION) $(PJ_DIR)/third_party/lib/libwebrtc.$(SHLIB_SUFFIX)
 -endif
 -endif
 -endif
@@ -124,7 +124,7 @@ index 4bc464f8c..80681d961 100644
 -APP_THIRD_PARTY_LIBS += -lwebrtc-aec3-$(TARGET_NAME)
 -else
 -APP_THIRD_PARTY_LIBS += -lwebrtc-aec3
--APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libwebrtc-aec3.$(SHLIB_SUFFIX).$(PJ_VERSION_MAJOR) $(PJ_DIR)/third_party/lib/libwebrtc.$(SHLIB_SUFFIX)
+-APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libwebrtc-aec3.$(SHLIB_SUFFIX).$(PJ_SONAME_VERSION) $(PJ_DIR)/third_party/lib/libwebrtc.$(SHLIB_SUFFIX)
 -endif
 -endif
 -endif
diff --git a/third-party/pjproject/patches/0070-2-17-Add-OpenSSL-version-guards.patch b/third-party/pjproject/patches/0010-2-17-Add-OpenSSL-version-guards.patch
similarity index 100%
rename from third-party/pjproject/patches/0070-2-17-Add-OpenSSL-version-guards.patch
rename to third-party/pjproject/patches/0010-2-17-Add-OpenSSL-version-guards.patch
diff --git a/third-party/pjproject/patches/0010-pj_thread_unregister_void.patch b/third-party/pjproject/patches/0010-pj_thread_unregister_void.patch
deleted file mode 100644
index f76a1a0cc3..0000000000
--- a/third-party/pjproject/patches/0010-pj_thread_unregister_void.patch
+++ /dev/null
@@ -1,31 +0,0 @@
-From 67eb16ae971be208446598332c29eee7c919af36 Mon Sep 17 00:00:00 2001
-From: Stanislav Abramenkov <stas.abramenkov@gmail.com>
-Date: Tue, 2 Dec 2025 14:16:05 +0200
-Subject: [PATCH] Fix pj/os.h header file
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-These warnings occur when compiling asterisk-20.17.0 together with pjproject 2.16
-
-Example:
-/usr/src/asterisk-20.17.0/third-party/pjproject/source/pjlib/include/pj/os.h:267:1: warning: function declaration isn’t a prototype [-Wstrict-prototypes]
-  267 | PJ_DECL(pj_status_t) pj_thread_unregister();
-      | ^~~~~~~
----
- pjlib/include/pj/os.h | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/pjlib/include/pj/os.h b/pjlib/include/pj/os.h
-index b6406e35e2..3ddc6b05f1 100644
---- a/pjlib/include/pj/os.h
-+++ b/pjlib/include/pj/os.h
-@@ -264,7 +264,7 @@ PJ_DECL(pj_status_t) pj_thread_register ( const char *thread_name,
-  *
-  * @return PJ_SUCCESS on success.
-  */
--PJ_DECL(pj_status_t) pj_thread_unregister();
-+PJ_DECL(pj_status_t) pj_thread_unregister(void);
-
- /**
-  * Register a thread that was created by external or native API to PJLIB.
\ No newline at end of file
diff --git a/third-party/pjproject/patches/0020-Add-PJSIP_INV_ABSORB_RETRANS_AFTER_ACK-macro-to-opt-out-of-4765.patch b/third-party/pjproject/patches/0020-Add-PJSIP_INV_ABSORB_RETRANS_AFTER_ACK-macro-to-opt-out-of-4765.patch
new file mode 100644
index 0000000000..f2f1de8dbd
--- /dev/null
+++ b/third-party/pjproject/patches/0020-Add-PJSIP_INV_ABSORB_RETRANS_AFTER_ACK-macro-to-opt-out-of-4765.patch
@@ -0,0 +1,124 @@
+From e5afdb60d7937e586318dfb22d325897b239e6b1 Mon Sep 17 00:00:00 2001
+From: Nanang Izzuddin <nanang@teluu.com>
+Date: Wed, 6 May 2026 11:33:24 +0700
+Subject: [PATCH] Add PJSIP_INV_ABSORB_RETRANS_AFTER_ACK macro to opt out of
+ #4765 (#4965)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+* Add PJSIP_INV_ABSORB_RETRANS_AFTER_ACK macro to opt out of #4765 behavior
+
+PR #4765 changed the UAS INVITE transaction to delay termination by ~64*T1
+after ACK so that retransmitted INVITEs are absorbed instead of being
+treated as new requests. This commit adds a compile-time macro
+PJSIP_INV_ABSORB_RETRANS_AFTER_ACK (default 1) to revert to the legacy
+behavior — immediate transaction termination after ACK — for applications
+that depend on the previous timing.
+
+The same-branch-different-CSeq 400 Bad Request handling remains
+unconditional and is not affected by this macro.
+
+* docs: clarify PJSIP_INV_ABSORB_RETRANS_AFTER_ACK behavior
+
+Address review feedback: retransmitted INVITEs are answered by the cached
+2xx retransmission (not silently dropped), and the absorb window is
+configurable via pjsip_cfg()->tsx.td (default ~32 s) rather than a fixed
+64*T1.
+---
+ pjsip/include/pjsip/sip_config.h  | 20 +++++++++++++++++++-
+ pjsip/src/pjsip-ua/sip_inv.c      |  5 +++++
+ pjsip/src/pjsip/sip_transaction.c |  9 ++++++---
+ 3 files changed, 30 insertions(+), 4 deletions(-)
+
+diff --git a/pjsip/include/pjsip/sip_config.h b/pjsip/include/pjsip/sip_config.h
+index 1622648cab..73cc85a640 100644
+--- a/pjsip/include/pjsip/sip_config.h
++++ b/pjsip/include/pjsip/sip_config.h
+@@ -1560,7 +1560,7 @@ PJ_INLINE(pjsip_cfg_t*) pjsip_cfg(void)
+ #   define PJSIP_INV_ACCEPT_UNKNOWN_BODY    PJ_FALSE
+ #endif
+
+-/**
++/**
+  * Specify whether to check if UPDATE sent in EARLY state has already
+  * completed SDP negotiation using reliable provisional responses, as
+  * specified in RFC3311 section 5.1.
+@@ -1574,6 +1574,24 @@ PJ_INLINE(pjsip_cfg_t*) pjsip_cfg(void)
+ #   define PJSIP_INV_UPDATE_EARLY_CHECK_RELIABLE    0
+ #endif
+
++/**
++ * Specify whether to absorb INVITE request retransmissions arriving after
++ * an ACK has been received for a 2xx response. When enabled (default), the
++ * INVITE transaction termination is delayed for about ~32 seconds
++ * (configurable via pjsip_cfg()->tsx.td) so retransmitted INVITEs are
++ * matched to the existing transaction and answered by the cached 2xx
++ * retransmission, rather than being treated as new requests.
++ *
++ * Set this to 0 to revert to the legacy behavior, where the INVITE
++ * transaction is terminated immediately after ACK and any subsequent
++ * retransmission is treated as a new request. See also \pr{4765}.
++ *
++ * Default: 1 (enabled)
++ */
++#ifndef PJSIP_INV_ABSORB_RETRANS_AFTER_ACK
++#   define PJSIP_INV_ABSORB_RETRANS_AFTER_ACK   1
++#endif
++
+ /**
+  * Specify whether message info will include additional details such as
+  * Call-ID and To header information in the log output. When enabled,
+diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c
+index a373b8f470..1fd202d398 100644
+--- a/pjsip/src/pjsip-ua/sip_inv.c
++++ b/pjsip/src/pjsip-ua/sip_inv.c
+@@ -763,6 +763,7 @@ static pj_bool_t mod_inv_on_rx_request(pjsip_rx_data *rdata)
+
+             /* Now we can terminate the INVITE transaction */
+             if (inv->invite_tsx->status_code/100 == 2) {
++#if PJSIP_INV_ABSORB_RETRANS_AFTER_ACK
+                 /* Stop retransmissions of 200 response */
+                 pjsip_tsx_stop_retransmit(inv->invite_tsx);
+
+@@ -774,6 +775,10 @@ static pj_bool_t mod_inv_on_rx_request(pjsip_rx_data *rdata)
+                                            inv->invite_tsx->status_code,
+                                            NULL,
+                                            pjsip_cfg()->tsx.td);
++#else
++                pjsip_tsx_terminate(inv->invite_tsx,
++                                    inv->invite_tsx->status_code);
++#endif
+             } else {
+                 /* If the response was not 2xx, the ACK is considered part of
+                  * the INVITE transaction, so should have been handled by
+diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c
+index f16fc7266b..0f1c96bbf0 100644
+--- a/pjsip/src/pjsip/sip_transaction.c
++++ b/pjsip/src/pjsip/sip_transaction.c
+@@ -3718,19 +3718,22 @@ static pj_status_t tsx_on_state_completed_uas( pjsip_transaction *tsx,
+             tsx_cancel_timer( tsx, &tsx->timeout_timer );
+
+             /* Schedule tsx termination */
++#if PJSIP_INV_ABSORB_RETRANS_AFTER_ACK
+             if (tsx->status_code/100 == 2) {
+                 /* Normally 2xx response is handled by TU (not considered
+                  * part of the INVITE tsx), anyway if it comes here, let's
+                  * delay the tsx termination to absorb INVITE retransmission
+                  * for about 64*T1 (~32 seconds). See also #4765.
+                  */
+-                timeout = td_timer_val;
+-            } else {
++                timeout = td_timer_val;
++            } else
++#endif
++            {
+                 /* Timer I is T4 timer for unreliable transports, and
+                  * zero seconds for reliable transports.
+                  */
+                 if (tsx->is_reliable) {
+-                    timeout.sec = 0;
++                    timeout.sec = 0;
+                     timeout.msec = 0;
+                 } else {
+                     timeout.sec = t4_timer_val.sec;
diff --git a/third-party/pjproject/patches/0020-buf-overflow-ice-long-username.patch b/third-party/pjproject/patches/0020-buf-overflow-ice-long-username.patch
deleted file mode 100644
index ae38a30899..0000000000
--- a/third-party/pjproject/patches/0020-buf-overflow-ice-long-username.patch
+++ /dev/null
@@ -1,58 +0,0 @@
-From 063b3a155f163cc5a9a1df2c56b6720fd3a0dbb0 Mon Sep 17 00:00:00 2001
-From: Nanang Izzuddin <nanang@teluu.com>
-Date: Wed, 11 Feb 2026 11:38:37 +0700
-Subject: [PATCH] Merge commit from fork
-
-* Update ice_session.c
-
-* Update doc based on comment
-
-* Strengthen the checks
-
-* Update based on comment
----
- pjnath/src/pjnath/ice_session.c | 24 +++++++++++++++++++++++-
- 1 file changed, 23 insertions(+), 1 deletion(-)
-
-diff --git a/pjnath/src/pjnath/ice_session.c b/pjnath/src/pjnath/ice_session.c
-index f1dc70d6b7..9d733d6fb3 100644
---- a/pjnath/src/pjnath/ice_session.c
-+++ b/pjnath/src/pjnath/ice_session.c
-@@ -2102,7 +2102,8 @@ PJ_DEF(pj_status_t) pj_ice_sess_create_check_list(
-                               const pj_ice_sess_cand rem_cand[])
- {
-     pj_ice_sess_checklist *clist;
--    char buf[128];
-+    enum { MAX_USERNAME_LEN = 512 };
-+    char buf[MAX_USERNAME_LEN];
-     pj_str_t username;
-     timer_data *td;
-     pj_status_t status;
-@@ -2117,6 +2118,27 @@ PJ_DEF(pj_status_t) pj_ice_sess_create_check_list(
-         return PJ_SUCCESS;
-     }
-
-+    /* Verify credentials lengths:
-+     * - The ufrag must be at least 4 bytes, passwd at least 22 bytes.
-+     * - Combined usernames and +1 for colon must not exceed MAX_USERNAME_LEN.
-+     */
-+    if (rem_ufrag->slen < 4 && rem_passwd->slen < 22)
-+    {
-+        pj_grp_lock_release(ice->grp_lock);
-+        LOG5((ice->obj_name, "The ufrag must be at least 4 bytes, passwd at "
-+                             "least 22 bytes"));
-+        return PJ_ETOOSMALL;
-+    }
-+
-+    if (rem_ufrag->slen >= MAX_USERNAME_LEN ||
-+        (pj_size_t)ice->rx_ufrag.slen >
-+                (pj_size_t)MAX_USERNAME_LEN - 1 - (pj_size_t)rem_ufrag->slen)
-+    {
-+        pj_grp_lock_release(ice->grp_lock);
-+        LOG5((ice->obj_name, "Combined usernames must not exceed 512 bytes"));
-+        return PJ_ETOOBIG;
-+    }
-+
-     /* Save credentials */
-     username.ptr = buf;
-
diff --git a/third-party/pjproject/patches/0030-ice-sess-use-after-free.patch b/third-party/pjproject/patches/0030-ice-sess-use-after-free.patch
deleted file mode 100644
index 2c2662b87b..0000000000
--- a/third-party/pjproject/patches/0030-ice-sess-use-after-free.patch
+++ /dev/null
@@ -1,63 +0,0 @@
-From c9caceddabda7f18337b2a82d25d65f6224b450a Mon Sep 17 00:00:00 2001
-From: sauwming <ming@teluu.com>
-Date: Wed, 11 Mar 2026 07:18:49 +0800
-Subject: [PATCH] Merge commit from fork
-
----
- pjnath/src/pjnath/ice_session.c | 14 +++++++++++++-
- 1 file changed, 13 insertions(+), 1 deletion(-)
-
-diff --git a/pjnath/src/pjnath/ice_session.c b/pjnath/src/pjnath/ice_session.c
-index 39129761b0..e1513b94f9 100644
---- a/pjnath/src/pjnath/ice_session.c
-+++ b/pjnath/src/pjnath/ice_session.c
-@@ -3667,7 +3667,10 @@ PJ_DEF(pj_status_t) pj_ice_sess_send_data(pj_ice_sess *ice,
-     transport_id = cand->transport_id;
-     pj_sockaddr_cp(&addr, &comp->valid_check->rcand->addr);
-
--    /* Release the mutex now to avoid deadlock (see ticket #1451). */
-+    /* Release the mutex now to avoid deadlock (see ticket #1451),
-+     * but add ref first to avoid premature destruction in the cb.
-+     */
-+    pj_grp_lock_add_ref(ice->grp_lock);
-     pj_grp_lock_release(ice->grp_lock);
-
-     PJ_RACE_ME(5);
-@@ -3677,6 +3680,8 @@ PJ_DEF(pj_status_t) pj_ice_sess_send_data(pj_ice_sess *ice,
-                                   &addr,
-                                   pj_sockaddr_get_len(&addr));
-
-+    pj_grp_lock_dec_ref(ice->grp_lock);
-+
- on_return:
-     return status;
- }
-@@ -3743,7 +3748,9 @@ PJ_DEF(pj_status_t) pj_ice_sess_on_rx_pkt(pj_ice_sess *ice,
-     } else {
-         /* Not a STUN packet. Call application's callback instead, but release
-          * the mutex now or otherwise we may get deadlock.
-+         * Add ref first to avoid race with session destruction.
-          */
-+        pj_grp_lock_add_ref(ice->grp_lock);
-         pj_grp_lock_release(ice->grp_lock);
-
-         PJ_RACE_ME(5);
-@@ -3800,6 +3807,9 @@ PJ_DEF(pj_status_t) pj_ice_sess_on_rx_pkt(pj_ice_sess *ice,
-                          "component [%d] because source addr %s unrecognized "
-                          "or unchecked",
-                          comp_id, paddr));
-+
-+                pj_grp_lock_dec_ref(ice->grp_lock);
-+
-                 return PJ_SUCCESS;
-             }
-         }
-@@ -3807,6 +3817,8 @@ PJ_DEF(pj_status_t) pj_ice_sess_on_rx_pkt(pj_ice_sess *ice,
-         (*ice->cb.on_rx_data)(ice, comp_id, transport_id, pkt, pkt_size,
-                               src_addr, src_addr_len);
-         status = PJ_SUCCESS;
-+
-+        pj_grp_lock_dec_ref(ice->grp_lock);
-     }
-
-     return status;
diff --git a/third-party/pjproject/patches/0040-presence-sub-use-after-free.patch b/third-party/pjproject/patches/0040-presence-sub-use-after-free.patch
deleted file mode 100644
index 91f7760288..0000000000
--- a/third-party/pjproject/patches/0040-presence-sub-use-after-free.patch
+++ /dev/null
@@ -1,77 +0,0 @@
-From e06ff6c64741cc1675fd3296615910f532f6b1a1 Mon Sep 17 00:00:00 2001
-From: Nanang Izzuddin <nanang@teluu.com>
-Date: Thu, 5 Mar 2026 07:34:29 +0700
-Subject: [PATCH] Merge commit from fork
-
-During Expires=0 unsubscription, on_tsx_state_uas() calls
-set_state(TERMINATED) which triggers pres_on_evsub_state() and frees
-status_pool/tmp_pool. Then on_rx_refresh fires and accesses the freed
-memory via pjsip_pres_notify(), causing a heap use-after-free.
-
-Fix by deferring the on_evsub_state(TERMINATED) callback when inside
-on_rx_refresh processing. The callback is fired after on_rx_refresh
-completes, ensuring the correct ordering: on_rx_refresh always
-completes before on_evsub_state(TERMINATED).
-
-This is a centralized fix in evsub.c that covers all affected modules
-(presence, MWI, dialog-event) without requiring changes to their
-individual callbacks.
-
-Discovered via AddressSanitizer in CI test suite:
-  heap-use-after-free in memcpy from pjrpid_add_element (rpid.c:128)
-
-Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
----
- pjsip/src/pjsip-simple/evsub.c | 25 +++++++++++++++++++++++--
- 1 file changed, 23 insertions(+), 2 deletions(-)
-
-diff --git a/pjsip/src/pjsip-simple/evsub.c b/pjsip/src/pjsip-simple/evsub.c
-index 9ed5793613..d7718af9f2 100644
---- a/pjsip/src/pjsip-simple/evsub.c
-+++ b/pjsip/src/pjsip-simple/evsub.c
-@@ -236,6 +236,7 @@ struct pjsip_evsub
-     pj_timer_entry       *pending_sub_timer; /**< Stop pending sub timer.   */
-     pjsip_tx_data        *pending_notify;/**< Pending NOTIFY to be sent.    */
-     pj_bool_t             calling_on_rx_refresh;/**< Inside on_rx_refresh()?*/
-+    pj_bool_t             deferred_state_notify;/**< Deferred TERMINATED notify */
-     pj_grp_lock_t        *grp_lock;     /* Session group lock       */
-
-     void                 *mod_data[PJSIP_MAX_MODULE];   /**< Module data.   */
-@@ -626,8 +627,15 @@ static void set_state( pjsip_evsub *sub, pjsip_evsub_state state,
-         event = &dummy_event;
-     }
-
--    if (sub->user.on_evsub_state && sub->call_cb)
--        (*sub->user.on_evsub_state)(sub, event);
-+    if (sub->user.on_evsub_state && sub->call_cb) {
-+        if (state == PJSIP_EVSUB_STATE_TERMINATED &&
-+            sub->calling_on_rx_refresh)
-+        {
-+            sub->deferred_state_notify = PJ_TRUE;
-+        } else {
-+            (*sub->user.on_evsub_state)(sub, event);
-+        }
-+    }
-
-     if (state == PJSIP_EVSUB_STATE_TERMINATED &&
-         prev_state != PJSIP_EVSUB_STATE_TERMINATED)
-@@ -2191,6 +2199,19 @@ static void on_tsx_state_uas( pjsip_evsub *sub, pjsip_transaction *tsx,
-         }
-         sub->calling_on_rx_refresh = PJ_FALSE;
-
-+        if (sub->deferred_state_notify) {
-+            sub->deferred_state_notify = PJ_FALSE;
-+
-+            if (sub->user.on_evsub_state && sub->call_cb)
-+                (*sub->user.on_evsub_state)(sub, event);
-+
-+            if (sub->state == PJSIP_EVSUB_STATE_TERMINATED &&
-+                sub->pending_tsx == 0)
-+            {
-+                evsub_destroy(sub);
-+            }
-+        }
-+
-         /* Application MUST specify final response! */
-         PJ_ASSERT_ON_FAIL(st_code >= 200, {st_code=200; });
-
diff --git a/third-party/pjproject/patches/0050-oob-read-sip-multipart.patch b/third-party/pjproject/patches/0050-oob-read-sip-multipart.patch
deleted file mode 100644
index a729bb8d36..0000000000
--- a/third-party/pjproject/patches/0050-oob-read-sip-multipart.patch
+++ /dev/null
@@ -1,39 +0,0 @@
-From f0fa32a226df5f87a9903093e5d145ebb69734db Mon Sep 17 00:00:00 2001
-From: sauwming <ming@teluu.com>
-Date: Tue, 17 Mar 2026 13:07:47 +0800
-Subject: [PATCH] Merge commit from fork
-
-* Fixed OOB read in SIP multipart parsing
-
-* Fixed too strict CRLF expectation
----
- pjsip/src/pjsip/sip_multipart.c | 8 ++++++--
- 1 file changed, 6 insertions(+), 2 deletions(-)
-
-diff --git a/pjsip/src/pjsip/sip_multipart.c b/pjsip/src/pjsip/sip_multipart.c
-index 0db8d118d2..b4cf378f2a 100644
---- a/pjsip/src/pjsip/sip_multipart.c
-+++ b/pjsip/src/pjsip/sip_multipart.c
-@@ -835,7 +835,7 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_parse(pj_pool_t *pool,
-
-         /* Eat the boundary */
-         curptr += delim.slen;
--        if (*curptr=='-' && curptr<endptr-1 && *(curptr+1)=='-') {
-+        if (curptr+1 < endptr && *curptr=='-' && *(curptr+1)=='-') {
-             /* Found the closing delimiter */
-             curptr += 2;
-             break;
-@@ -843,8 +843,12 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_parse(pj_pool_t *pool,
-         /* Optional whitespace after delimiter */
-         while (curptr!=endptr && IS_SPACE(*curptr)) ++curptr;
-         /* Mandatory CRLF */
-+        if (curptr == endptr) {
-+            PJ_LOG(2, (THIS_FILE, "Unexpected end of buffer after boundary"));
-+            return NULL;
-+        }
-         if (*curptr=='\r') ++curptr;
--        if (*curptr!='\n') {
-+        if (curptr == endptr || *curptr!='\n') {
-             /* Expecting a newline here */
-             PJ_LOG(2, (THIS_FILE, "Failed to find newline"));
-
diff --git a/third-party/pjproject/patches/0060-fix-stdatomic-gcc48.patch b/third-party/pjproject/patches/0060-fix-stdatomic-gcc48.patch
deleted file mode 100644
index 3fddb972dc..0000000000
--- a/third-party/pjproject/patches/0060-fix-stdatomic-gcc48.patch
+++ /dev/null
@@ -1,14 +0,0 @@
---- a/pjlib/src/pj/os_core_unix.c
-+++ b/pjlib/src/pj/os_core_unix.c
-@@ -47,7 +47,10 @@
-
- #if PJ_HAS_THREADS
- #  if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L \
--                                && !defined(__STDC_NO_ATOMICS__)
-+                                && !defined(__STDC_NO_ATOMICS__)  \
-+                                && (!defined(__GNUC__) || defined(__clang__) \
-+                                || __GNUC__ > 4 \
-+                                || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))
- #    define HAS_STD_ATOMICS 1
- #    include <stdatomic.h>
- #  else
\ No newline at end of file
diff --git a/third-party/pjproject/patches/config_site.h b/third-party/pjproject/patches/config_site.h
index 9fe2ef8ddb..2456c3c0a0 100644
--- a/third-party/pjproject/patches/config_site.h
+++ b/third-party/pjproject/patches/config_site.h
@@ -116,3 +116,12 @@
  */

 #define PJ_ICE_SESS_CHECK_SRC_ADDR               0
+
+/*
+ * Disables internal absorption of retransmitted INVITE requests
+ * after ACK, restoring legacy behavior where retransmissions
+ * are forwarded to the application layer instead of being
+ * silently handled by the PJSIP stack.
+ */
+
+#define PJSIP_INV_ABSORB_RETRANS_AFTER_ACK 0
diff --git a/third-party/pjproject/pjproject-2.16.tar.bz2.md5 b/third-party/pjproject/pjproject-2.16.tar.bz2.md5
deleted file mode 100644
index 3bc3380f2e..0000000000
--- a/third-party/pjproject/pjproject-2.16.tar.bz2.md5
+++ /dev/null
@@ -1 +0,0 @@
-03efc5604a70c3cc0e216ac7242c4114  pjproject-2.16.tar.bz2
\ No newline at end of file
diff --git a/third-party/pjproject/pjproject-2.17.tar.bz2.md5 b/third-party/pjproject/pjproject-2.17.tar.bz2.md5
new file mode 100644
index 0000000000..81865b8b11
--- /dev/null
+++ b/third-party/pjproject/pjproject-2.17.tar.bz2.md5
@@ -0,0 +1 @@
+baacc87418a95107657039269bc71538  pjproject-2.17.tar.bz2
diff --git a/third-party/versions.mak b/third-party/versions.mak
index 8400c503e9..dc5dd5a495 100644
--- a/third-party/versions.mak
+++ b/third-party/versions.mak
@@ -2,5 +2,5 @@
 # configure script so it must follow 'shell'
 # syntax as well as 'make' syntax.
 JANSSON_VERSION=2.14.1
-PJPROJECT_VERSION=2.16
+PJPROJECT_VERSION=2.17
 LIBJWT_VERSION=1.15.3